@gfdelarue/testkit 0.4.0 → 0.4.2

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.
Files changed (124) hide show
  1. package/README.md +125 -0
  2. package/bin/testkit-windows-owner-arm64.exe +0 -0
  3. package/bin/testkit-windows-owner-x64.exe +0 -0
  4. package/dist/app/bugs/areas.d.ts +38 -0
  5. package/dist/app/bugs/areas.js +1 -0
  6. package/dist/app/bugs/assessment.d.ts +9 -0
  7. package/dist/app/bugs/assessment.js +1 -0
  8. package/dist/app/bugs/categories.d.ts +41 -0
  9. package/dist/app/bugs/categories.js +1 -0
  10. package/dist/app/bugs/ci-report.d.ts +21 -0
  11. package/dist/app/bugs/ci-report.js +2 -0
  12. package/dist/app/bugs/ci-summary.d.ts +13 -0
  13. package/dist/app/bugs/ci-summary.js +5 -0
  14. package/dist/app/bugs/draft.d.ts +70 -0
  15. package/dist/app/bugs/draft.js +4 -0
  16. package/dist/app/bugs/facts.d.ts +48 -0
  17. package/dist/app/bugs/facts.js +2 -0
  18. package/dist/app/bugs/file.d.ts +45 -0
  19. package/dist/app/bugs/file.js +1 -0
  20. package/dist/app/bugs/gate.d.ts +22 -0
  21. package/dist/app/bugs/gate.js +1 -0
  22. package/dist/app/bugs/guide.d.ts +20 -0
  23. package/dist/app/bugs/guide.js +8 -0
  24. package/dist/app/bugs/issue.d.ts +18 -0
  25. package/dist/app/bugs/issue.js +6 -0
  26. package/dist/app/bugs/known-failures.d.ts +49 -0
  27. package/dist/app/bugs/known-failures.js +1 -0
  28. package/dist/app/bugs/record.d.ts +14 -0
  29. package/dist/app/bugs/record.js +2 -0
  30. package/dist/app/bugs/run-reading.d.ts +51 -0
  31. package/dist/app/bugs/run-reading.js +1 -0
  32. package/dist/app/bugs/store.d.ts +34 -0
  33. package/dist/app/bugs/store.js +2 -0
  34. package/dist/app/campaigns/brief.d.ts +7 -0
  35. package/dist/app/campaigns/brief.js +10 -0
  36. package/dist/app/campaigns/campaign.d.ts +14 -0
  37. package/dist/app/campaigns/campaign.js +1 -0
  38. package/dist/app/campaigns/index.d.ts +38 -0
  39. package/dist/app/campaigns/index.js +2 -0
  40. package/dist/app/campaigns/store.d.ts +5 -0
  41. package/dist/app/campaigns/store.js +1 -0
  42. package/dist/app/campaigns/summary.d.ts +13 -0
  43. package/dist/app/campaigns/summary.js +8 -0
  44. package/dist/app/run/engine-execution.d.ts +1 -0
  45. package/dist/app/run/engine-execution.js +1 -1
  46. package/dist/app/run/failure-details.d.ts +1 -0
  47. package/dist/app/run/failure-details.js +1 -1
  48. package/dist/app/tracker/index.d.ts +8 -0
  49. package/dist/app/tracker/index.js +1 -0
  50. package/dist/app/tracker/remote.d.ts +13 -0
  51. package/dist/app/tracker/remote.js +1 -0
  52. package/dist/app/tracker/token.d.ts +7 -0
  53. package/dist/app/tracker/token.js +5 -0
  54. package/dist/cli/command-catalog.d.ts +26 -0
  55. package/dist/cli/command-catalog.js +1 -1
  56. package/dist/cli/command-registry.js +1 -1
  57. package/dist/cli/commands/bug/file.d.ts +16 -0
  58. package/dist/cli/commands/bug/file.js +2 -0
  59. package/dist/cli/commands/bug/guide.d.ts +5 -0
  60. package/dist/cli/commands/bug/guide.js +1 -0
  61. package/dist/cli/commands/bug/record.d.ts +9 -0
  62. package/dist/cli/commands/bug/record.js +2 -0
  63. package/dist/cli/commands/campaign/finish.d.ts +10 -0
  64. package/dist/cli/commands/campaign/finish.js +1 -0
  65. package/dist/cli/commands/campaign/start.d.ts +7 -0
  66. package/dist/cli/commands/campaign/start.js +1 -0
  67. package/dist/cli/commands/campaign/status.d.ts +13 -0
  68. package/dist/cli/commands/campaign/status.js +2 -0
  69. package/dist/cli/commands/ci/report.d.ts +15 -0
  70. package/dist/cli/commands/ci/report.js +2 -0
  71. package/dist/cli/commands/run.d.ts +1 -1
  72. package/dist/cli/commands/run.js +2 -1
  73. package/dist/cli/renderers/bug/text.d.ts +8 -0
  74. package/dist/cli/renderers/bug/text.js +1 -0
  75. package/dist/cli/renderers/campaign/text.d.ts +2 -0
  76. package/dist/cli/renderers/campaign/text.js +1 -0
  77. package/dist/cli/renderers/ci/text.d.ts +2 -0
  78. package/dist/cli/renderers/ci/text.js +1 -0
  79. package/dist/config/authoring/index.d.ts +10 -0
  80. package/dist/config/loader/bugs.d.ts +15 -0
  81. package/dist/config/loader/bugs.js +1 -0
  82. package/dist/engines/adapters/execution.d.ts +4 -0
  83. package/dist/engines/adapters/execution.js +1 -1
  84. package/dist/engines/k6/adapter/executor.js +1 -1
  85. package/dist/engines/k6/runtime/checks.js +1 -1
  86. package/dist/engines/k6/runtime/http-assertions.d.ts +2 -2
  87. package/dist/engines/k6/runtime/http-assertions.js +1 -1
  88. package/dist/engines/k6/runtime/types.d.ts +2 -0
  89. package/dist/engines/node-vitest/adapter/evidence.js +2 -2
  90. package/dist/engines/node-vitest/adapter/failure-details.d.ts +1 -0
  91. package/dist/engines/node-vitest/adapter/failure-details.js +2 -2
  92. package/dist/engines/playwright/adapter/adapter.js +1 -1
  93. package/dist/engines/playwright/adapter/attachments.d.ts +1 -0
  94. package/dist/engines/playwright/adapter/config.js +3 -3
  95. package/dist/engines/playwright/adapter/evidence.js +1 -1
  96. package/dist/engines/playwright/adapter/reporter.js +1 -1
  97. package/dist/kernel/redaction.d.ts +1 -0
  98. package/dist/kernel/redaction.js +1 -1
  99. package/dist/process/execa-bundle.json +1 -1
  100. package/dist/process/files/json-file.d.ts +1 -0
  101. package/dist/process/files/json-file.js +1 -1
  102. package/dist/repo/history.d.ts +17 -0
  103. package/dist/repo/history.js +4 -0
  104. package/dist/repo/tree-listing.d.ts +10 -0
  105. package/dist/repo/tree-listing.js +1 -0
  106. package/dist/tracker/gitea.d.ts +14 -0
  107. package/dist/tracker/gitea.js +1 -0
  108. package/dist/tracker/github.d.ts +14 -0
  109. package/dist/tracker/github.js +1 -0
  110. package/dist/tracker/http.d.ts +33 -0
  111. package/dist/tracker/http.js +1 -0
  112. package/dist/tracker/index.d.ts +4 -0
  113. package/dist/tracker/index.js +1 -0
  114. package/dist/tracker/redaction.d.ts +4 -0
  115. package/dist/tracker/redaction.js +2 -0
  116. package/dist/tracker/requests.d.ts +17 -0
  117. package/dist/tracker/requests.js +1 -0
  118. package/dist/tracker/responses.d.ts +24 -0
  119. package/dist/tracker/responses.js +1 -0
  120. package/dist/tracker/types.d.ts +51 -0
  121. package/dist/tracker/types.js +1 -0
  122. package/docs/bug-records.md +303 -0
  123. package/package.json +2 -1
  124. package/testkit-config-schema.md +50 -0
@@ -0,0 +1 @@
1
+ import{BUG_GUIDE as r,renderBugGuide as t}from"../../../app/bugs/guide.js";export default function i({json:e,log:u}){return e||u(t(r)),{ok:!0,guide:r}}
@@ -0,0 +1,9 @@
1
+ import type { CommandContext } from "../../command-catalog.js";
2
+ export default function run({ args, flags, json, log }: CommandContext<"bug record">): Promise<{
3
+ draft: import("../../../app/bugs/draft.js").BugDraft;
4
+ path: string;
5
+ redactedFields: readonly string[];
6
+ replaced: boolean;
7
+ unreadableDrafts: readonly import("../../../app/bugs/store.js").UnreadableDraft[];
8
+ ok: boolean;
9
+ }>;
@@ -0,0 +1,2 @@
1
+ import{recordBug as n}from"../../../app/bugs/record.js";import{renderBugRecordResult as u}from"../../renderers/bug/text.js";export default async function f({args:e,flags:o,json:t,log:i}){const r=await n({dir:o.dir,file:e.file});return t||i(u(r).join(`
2
+ `)),{ok:!0,...r}}
@@ -0,0 +1,10 @@
1
+ import type { CommandContext } from "../../command-catalog.js";
2
+ export default function run({ flags, json, log }: CommandContext<"campaign finish">): Promise<{
3
+ campaign: import("../../../app/campaigns/campaign.js").Campaign;
4
+ filed: readonly import("../../../app/bugs/draft.js").BugDraft[];
5
+ headCommit: string;
6
+ summary: string;
7
+ summaryPath: string;
8
+ unfiled: readonly import("../../../app/bugs/draft.js").BugDraft[];
9
+ ok: boolean;
10
+ }>;
@@ -0,0 +1 @@
1
+ import{finishCampaign as a}from"../../../app/campaigns/index.js";export default async function u({flags:i,json:t,log:n}){const r=await a({dir:i.dir});return t||n(r.summary),{ok:!0,...r}}
@@ -0,0 +1,7 @@
1
+ import type { CommandContext } from "../../command-catalog.js";
2
+ export default function run({ flags, json, log }: CommandContext<"campaign start">): Promise<{
3
+ ok: boolean;
4
+ campaign: import("../../../app/campaigns/campaign.js").Campaign;
5
+ areas: import("../../../app/bugs/areas.js").AreaMapCoverage;
6
+ brief: string;
7
+ }>;
@@ -0,0 +1 @@
1
+ import{startCampaign as e}from"../../../app/campaigns/index.js";export default async function n({flags:a,json:t,log:i}){const r=await e({dir:a.dir,id:a.id,target:a.target});return t||i(r.brief),{ok:!0,campaign:r.campaign,areas:r.areas,brief:r.brief}}
@@ -0,0 +1,13 @@
1
+ import type { CommandContext } from "../../command-catalog.js";
2
+ export default function run({ flags, json, log }: CommandContext<"campaign status">): Promise<{
3
+ active: import("../../../app/campaigns/campaign.js").Campaign | null;
4
+ areas: {
5
+ readonly areaCount: number;
6
+ readonly doubleClaimCount: number;
7
+ readonly unclaimedShare: number;
8
+ };
9
+ drafts: readonly import("../../../app/bugs/draft.js").BugDraft[];
10
+ lastFinished: import("../../../app/campaigns/campaign.js").Campaign | null;
11
+ unreadableDrafts: readonly import("../../../app/bugs/store.js").UnreadableDraft[];
12
+ ok: boolean;
13
+ }>;
@@ -0,0 +1,2 @@
1
+ import{readCampaignStatus as i}from"../../../app/campaigns/index.js";import{renderCampaignStatus as o}from"../../renderers/campaign/text.js";export default async function u({flags:r,json:a,log:n}){const t=await i({dir:r.dir});return a||n(o(t).join(`
2
+ `)),{ok:!0,...t}}
@@ -0,0 +1,15 @@
1
+ import type { CommandContext } from "../../command-catalog.js";
2
+ export default function run({ flags, json, log }: CommandContext<"ci report">): Promise<{
3
+ comment: import("../../../tracker/types.js").TrackerCommentReference | null;
4
+ commit: string;
5
+ context: string;
6
+ description: string;
7
+ known: readonly import("../../../app/bugs/known-failures.js").KnownFailure[];
8
+ new: readonly import("../../../app/bugs/known-failures.js").FailedTest[];
9
+ pullRequest: number | null;
10
+ state: import("../../../tracker/types.js").TrackerCommitState;
11
+ stepSummary: string | null;
12
+ trackerProblem: string | null;
13
+ unreadableIssues: readonly import("../../../app/bugs/known-failures.js").UnreadableIssue[];
14
+ ok: boolean;
15
+ }>;
@@ -0,0 +1,2 @@
1
+ import{reportCiRun as n}from"../../../app/bugs/ci-report.js";import{renderCiReport as i}from"../../renderers/ci/text.js";export default async function p({flags:r,json:o,log:e}){const t=await n({context:r.context,dir:r.dir,env:process.env});return o||e(i(t).join(`
2
+ `)),{ok:!0,...t}}
@@ -1,5 +1,5 @@
1
1
  import type { CommandContext } from "../command-catalog.js";
2
- export default function run({ args, flags, json, setExitCode }: CommandContext<"run">): Promise<{
2
+ export default function run({ args, flags, json, log, setExitCode }: CommandContext<"run">): Promise<{
3
3
  exitCode: number;
4
4
  failure?: import("../../kernel/error-serialization.js").SerializedError;
5
5
  results: import("../../app/run/results.js").FinalizedServiceResult[];
@@ -1 +1,2 @@
1
- import{buildRunRequest as c}from"../../app/run/request.js";import{executeRunRequest as n}from"../operations/run/operation.js";export default async function p({args:r,flags:e,json:o,setExitCode:u}){const i=await c({dir:e.dir,service:e.service,taskConcurrency:e["task-concurrency"],workers:e.workers,fileTimeoutSeconds:e["file-timeout-seconds"],type:e.type,suite:e.suite,file:e.file,allowEmpty:e["allow-empty"],ignoreSkipRules:e["ignore-skip-rules"],skipSchemaSourceVerify:e["skip-schema-source-verify"]},r.type||null,process.cwd(),process.cwd()),t=await n(i,{outputMode:e["output-mode"]||"compact",json:o,debug:e.debug});return u(t.exitCode),t}
1
+ import{applyKnownFailures as s,loadBugPolicy as d}from"../../app/bugs/gate.js";import{buildRunRequest as l}from"../../app/run/request.js";import{executeRunRequest as a}from"../operations/run/operation.js";import{renderKnownFailures as m}from"../renderers/bug/text.js";export default async function w({args:i,flags:e,json:u,log:n,setExitCode:c}){const r=await l({dir:e.dir,service:e.service,taskConcurrency:e["task-concurrency"],workers:e.workers,fileTimeoutSeconds:e["file-timeout-seconds"],type:e.type,suite:e.suite,file:e.file,allowEmpty:e["allow-empty"],ignoreSkipRules:e["ignore-skip-rules"],skipSchemaSourceVerify:e["skip-schema-source-verify"]},i.type||null,process.cwd(),process.cwd()),p=await d(r.productDir),o=await a(r,{outputMode:e["output-mode"]||"compact",json:u,debug:e.debug}),t=await s({env:process.env,exitCode:o.exitCode,failedOutsideTests:o.failure!==void 0,policy:p,productDir:r.productDir,run:o.runArtifact});return t.knownFailures!==null&&(o.outputMode==="compact"||o.outputMode==="debug")&&n(m(t.knownFailures).join(`
2
+ `)),c(t.exitCode),t.knownFailures===null?o:{...o,...t}}
@@ -0,0 +1,8 @@
1
+ import type { BugIssueReference, FiledBug, ReadyBugFiling } from "../../../app/bugs/file.js";
2
+ import type { KnownFailureReport } from "../../../app/bugs/gate.js";
3
+ import type { RecordBugResult } from "../../../app/bugs/record.js";
4
+ export declare function renderBugRecordResult(result: RecordBugResult): string[];
5
+ export declare function renderAlreadyFiledBug(issue: BugIssueReference): string;
6
+ export declare function renderBugFileRun(ready: Pick<ReadyBugFiling, "draft" | "filing">): string;
7
+ export declare function renderBugFileResult(result: FiledBug): string[];
8
+ export declare function renderKnownFailures(report: KnownFailureReport): string[];
@@ -0,0 +1 @@
1
+ import{describeUnnamedFailure as s}from"../../../app/bugs/known-failures.js";import{formatShare as d}from"../../../app/campaigns/brief.js";const i=12,a=20;export function renderBugRecordResult(e){return[`Recorded ${e.draft.id}`,`Draft: ${e.path}`,...e.redactedFields.length===0?[]:[`Redacted secrets in: ${e.redactedFields.join(", ")}`],...e.replaced?["Replaced the earlier draft for this test."]:[],...e.unreadableDrafts.map(n=>`Testkit cannot read ${n.file}: ${n.problem}`),`Next: commit and push the test, then run \`testkit bug file ${e.draft.id}\`.`]}export function renderAlreadyFiledBug(e){return`Already filed as #${String(e.number)}: ${e.url}`}export function renderBugFileRun(e){return`Running ${e.draft.test.file} at ${e.filing.foundAt.slice(0,i)}. Testkit files the bug when the test fails.`}export function renderBugFileResult(e){return[`Filed #${String(e.issue.number)}: ${e.issue.url}`,...e.covers==="file"?[`Covers: the whole script ${e.draft.test.file}, not one test. While the issue is open, every named failure in that file and its script failure are known.`]:[],e.area===null?"Area: none, because testkit.config.ts has no areas":`Area: ${e.area.id}, ${d(e.area.share)} of source bytes`,e.introducedBy===null?"Introduced by: unknown, because this shallow clone stops before the commit that wrote the faulty lines":`Introduced by: ${e.introducedBy.slice(0,i)}`,`Draft: ${e.path}`]}export function renderKnownFailures(e){const n=e.known.length,r=n-a;return[...e.trackerProblem===null?[]:[`Testkit could not read the open bugs, so every failure counts as new: ${e.trackerProblem}`],e.new.length===0?`Known failures: ${String(n)}. Every failing test has an open bug, so the run passes.`:`New failures: ${String(e.new.length)}. Known failures: ${String(n)}. The run fails on the new failures.`,...e.known.slice(0,a).map(({failure:t,issue:o})=>` #${String(o.issue)} ${t.file}: ${t.title??s(t)}`),...r>0?[` and ${String(r)} more`]:[]]}
@@ -0,0 +1,2 @@
1
+ import type { CampaignStatus } from "../../../app/campaigns/index.js";
2
+ export declare function renderCampaignStatus(status: CampaignStatus): string[];
@@ -0,0 +1 @@
1
+ import{formatCount as s,formatShare as o}from"../../../app/campaigns/brief.js";export function renderCampaignStatus(a){const{active:i,areas:r,lastFinished:n}=a,t=[];if(i===null)t.push("No campaign is active."),n?.finishedAt!==void 0&&t.push(`Last finished: ${n.id}, finished ${n.finishedAt}`);else{t.push(`Active campaign: ${i.id}`,` Target: ${i.target}`,` Started: ${i.startedAt}`,` Start commit: ${i.startCommit}`,`Recorded bugs: ${String(a.drafts.length)}`);for(const e of a.drafts)t.push(` ${e.severity}: ${e.title}`,` Test: ${e.test.file}`,` Issue: ${e.filed?.url??`not filed, draft ${e.id}`}`)}for(const e of a.unreadableDrafts)t.push(`Testkit cannot read ${e.file}: ${e.problem}`);return t.push(r.areaCount===0?"Area map: testkit.config.ts has no areas.":`Area map: ${o(r.unclaimedShare)} of source bytes unclaimed, ${s(r.doubleClaimCount,"double-claimed file")}.`),t}
@@ -0,0 +1,2 @@
1
+ import type { CiReport } from "../../../app/bugs/ci-report.js";
2
+ export declare function renderCiReport(report: CiReport): string[];
@@ -0,0 +1 @@
1
+ const t=12;export function renderCiReport(e){return[`Status ${e.context} on ${e.commit.slice(0,12)}: ${e.state}, ${e.description}`,e.comment===null?"Pull request comment: none, because this run has no pull request":`Pull request comment: ${e.comment.created?"created":"updated"} ${e.comment.url}`,...e.stepSummary===null?[]:["Step summary: written"],...e.trackerProblem===null?[]:[`Testkit could not read the open bugs, so every failure counts as new: ${e.trackerProblem}`]]}
@@ -264,7 +264,16 @@ export interface NodeNextPresetOptions {
264
264
  /** @deprecated Use taskConcurrency. Removed in 0.3.0. */
265
265
  workers?: number;
266
266
  }
267
+ export interface AreaConfig {
268
+ name: string;
269
+ paths: string[];
270
+ }
271
+ export interface BugsConfig {
272
+ knownFailures?: "allow" | "fail";
273
+ repository?: string;
274
+ }
267
275
  export interface TestkitConfig {
276
+ areas?: Record<string, AreaConfig>;
268
277
  artifacts?: {
269
278
  publication?: {
270
279
  directory: string;
@@ -276,6 +285,7 @@ export interface TestkitConfig {
276
285
  preserveSuites?: string[];
277
286
  };
278
287
  };
288
+ bugs?: BugsConfig;
279
289
  discovery?: DiscoveryConfig;
280
290
  execution?: TestkitExecutionConfig;
281
291
  fingerprints?: FingerprintConfig;
@@ -0,0 +1,15 @@
1
+ export declare const AREA_ID_PATTERN: RegExp;
2
+ export declare const AREA_ID_MAX_LENGTH = 40;
3
+ export interface AreaDefinition {
4
+ readonly id: string;
5
+ readonly name: string;
6
+ readonly paths: readonly string[];
7
+ }
8
+ export type KnownFailuresPolicy = "allow" | "fail";
9
+ export interface BugPolicy {
10
+ readonly knownFailures: KnownFailuresPolicy;
11
+ readonly repository: string | null;
12
+ }
13
+ export declare function isAreaId(value: unknown): value is string;
14
+ export declare function readAreaMap(config: object): readonly AreaDefinition[];
15
+ export declare function readBugPolicy(config: object): BugPolicy;
@@ -0,0 +1 @@
1
+ import{attachErrorDiagnostics as h}from"../../kernel/error-serialization.js";import{isUnknownRecord as i}from"../../kernel/unknown-values.js";export const AREA_ID_PATTERN=/^[a-z0-9]+(?:-[a-z0-9]+)*$/u,AREA_ID_MAX_LENGTH=40;const a=60;export function isAreaId(t){return typeof t=="string"&&t.length<=AREA_ID_MAX_LENGTH&&AREA_ID_PATTERN.test(t)}export function readAreaMap(t){const e=i(t)?t.areas:void 0;if(e===void 0)return[];if(!i(e))throw s("areas","areas must be an object keyed by area id");return Object.entries(e).map(([n,r])=>f(n,r))}export function readBugPolicy(t){const e=i(t)?t.bugs:void 0;if(e===void 0)return{knownFailures:"fail",repository:null};if(!i(e))throw s("bugs","bugs must be an object");u(e,["knownFailures","repository"],"bugs");const n=e.knownFailures??"fail";if(n!=="allow"&&n!=="fail")throw s("bugs.knownFailures",'bugs.knownFailures must be "fail" or "allow"');const r=e.repository??null;if(r!==null&&(typeof r!="string"||!/^\S+$/u.test(r)))throw s("bugs.repository",`bugs.repository must be "owner/name" or the repository's URL`);return{knownFailures:n,repository:r}}function f(t,e){const n=`areas.${t}`;if(!isAreaId(t))throw s(n,`area id ${JSON.stringify(t)} must be lowercase letters, digits, and single hyphens, at most ${String(AREA_ID_MAX_LENGTH)} characters`);if(!i(e))throw s(n,`${n} must be an object with name and paths`);u(e,["name","paths"],n);const r=e.name;if(typeof r!="string"||r.trim().length===0||r.length>a||/[\r\n]/u.test(r))throw s(`${n}.name`,`${n}.name must be one line of 1 to ${String(a)} characters`);const o=e.paths;if(!Array.isArray(o)||o.length===0)throw s(`${n}.paths`,`${n}.paths must list at least one glob`);return{id:t,name:r.trim(),paths:o.map(c=>l(c,`${n}.paths`))}}function l(t,e){if(typeof t!="string"||t.length===0||t.startsWith("/")||t.startsWith("!")||t.includes("\\")||t.split("/").includes(".."))throw s(e,`${e} entries must be globs relative to the repository root, written with "/", with no "..", and with no leading "!" or "/"; got ${JSON.stringify(t)}`);return t.replace(/^\.\//u,"")}function u(t,e,n){const r=Object.keys(t).filter(o=>!e.includes(o));if(r.length>0)throw s(`${n}.${r[0]}`,`${n} has no key ${JSON.stringify(r[0])}; it accepts ${e.join(" and ")}`)}function s(t,e){return h(Object.assign(new Error(e),{code:"invalid_bug_config"}),{source:"local",field:t})}
@@ -27,6 +27,10 @@ export declare function taskBeforeStartOutcome(task: EngineExecutionTask, signal
27
27
  export declare function taskTimeoutFailure(timeoutMs: number, identity: EngineFailureIdentity): EngineFailureEvidence;
28
28
  export declare function managedProcessFailure(result: ManagedProcessResult, identity: EngineFailureIdentity, evidenceError?: Error | null): EngineFailureEvidence | null;
29
29
  export declare function classifyManagedProcessResult(result: ManagedProcessResult, timeoutMs: number, deadlineExceeded: boolean, identity: EngineFailureIdentity): ProcessClassification | null;
30
+ export declare function processExitDetails(result: ManagedProcessResult): {
31
+ exitCode?: number;
32
+ exitSignal?: string;
33
+ };
30
34
  export declare function processExitFailure(result: ManagedProcessResult, identity: EngineFailureIdentity, selectedMessage?: string | null, fallbackLabel?: string): ProcessClassification;
31
35
  export declare function firstOutputLine(output: string): string | null;
32
36
  export declare function terminalOutcome(taskId: string, status: TestOutcomeStatus, startedAtMs: number, evidence: EngineEvidence, failure: EngineFailureEvidence | null): EngineTaskOutcome;
@@ -1 +1 @@
1
- import{isEngineTaskDeadlineExceededReason as d}from"../api/index.js";import"../../domain/test-outcomes.js";export async function executeEngineTasks(e,t,n,i,o){const r=[];for(const a of t){e.observer?.taskStarted(a.task);const s=Date.now(),c=taskBeforeStartOutcome(a.task,e.signal,s,n);c!==null?r.push(c):a.preparationFailure!==null?r.push(terminalOutcome(a.task.id,"failed",s,emptyEvidence(),a.preparationFailure)):r.push(await i(a,s))}for(const[a,s]of r.entries())e.observer?.evidenceRecorded(t[a].task.id,s.evidence),e.observer?.taskFinished(s);return{evidence:{artifacts:r.flatMap(a=>a.evidence.artifacts),logs:r.flatMap(a=>a.evidence.logs),metrics:r.flatMap(a=>a.evidence.metrics),native:o},invocationId:e.invocationId,outcomes:r}}export function taskBeforeStartOutcome(e,t,n,i){return e.deadlineMs<=n||d(t.reason)?terminalOutcome(e.id,"failed",n,emptyEvidence(),taskTimeoutFailure(e.timeoutMs,i)):t.aborted?terminalOutcome(e.id,"cancelled",n,emptyEvidence(),{code:`${i.codePrefix}_CANCELLED`,message:`${i.label} task was cancelled before its process started`}):null}export function taskTimeoutFailure(e,t){return{code:`${t.codePrefix}_TASK_TIMEOUT`,details:{timeoutMs:e},message:`${t.label} task exceeded its ${e.toString()}ms timeout`}}export function managedProcessFailure(e,t,n=null){return e.terminationError===null?null:{classification:"infra",code:`${t.codePrefix}_PROCESS_TERMINATION_INCOMPLETE`,details:{cancelled:e.cancelled,...n===null?{}:{evidenceCollectionFailure:errorDetails(n)},outputLimitExceeded:e.outputLimitExceeded,timedOut:e.timedOut},message:`${t.label} managed-process termination was incomplete: ${e.terminationError.message}`,...e.terminationError.stack===void 0?{}:{stack:e.terminationError.stack}}}export function classifyManagedProcessResult(e,t,n,i){const o=a=>({failure:a,status:"failed"}),r=managedProcessFailure(e,i);return r!==null?o(r):e.timedOut||n?o(taskTimeoutFailure(t,i)):e.cancelled?{failure:{code:`${i.codePrefix}_CANCELLED`,message:`${i.label} task was cancelled`},status:"cancelled"}:e.outputLimitExceeded?o({code:`${i.codePrefix}_OUTPUT_LIMIT`,message:`${i.label} task exceeded the bounded stdout/stderr capture limit`}):e.error!==null?o({...failureFromError(e.error,`${i.codePrefix}_PROCESS_START_FAILED`),classification:"infra"}):null}export function processExitFailure(e,t,n=null,i=t.label){return{failure:{code:`${t.codePrefix}_PROCESS_FAILED`,details:{...e.exitCode===null?{}:{exitCode:e.exitCode},...e.exitSignal===null?{}:{exitSignal:e.exitSignal}},message:n??(e.exitCode===null?`${i} process terminated by ${e.exitSignal??"an unknown signal"}`:`${i} process exited with code ${e.exitCode.toString()}`)},status:"failed"}}export function firstOutputLine(e){const t=e.split(/\r?\n/u).map(n=>n.trim()).find(Boolean);return t===void 0?null:t.slice(0,4e3)}export function terminalOutcome(e,t,n,i,o){const r=Date.now();return{durationMs:Math.max(0,r-n),evidence:i,failure:o,finishedAtMs:r,startedAtMs:n,status:t,taskId:e}}export function processOutputEvidence(e){return{artifacts:[],logs:[...e.stdout.length===0?[]:[{stream:"stdout",text:e.stdout}],...e.stderr.length===0?[]:[{stream:"stderr",text:e.stderr}]],metrics:[]}}export function emptyEvidence(){return{artifacts:[],logs:[],metrics:[]}}export function failureFromError(e,t){const n=toError(e);return{code:t,message:n.message,...n.stack===void 0?{}:{stack:n.stack}}}export function errorDetails(e){return{message:e.message,name:e.name,...e.stack===void 0?{}:{stack:e.stack}}}export function toError(e){return e instanceof Error?e:new Error(String(e))}
1
+ import{isEngineTaskDeadlineExceededReason as d}from"../api/index.js";import"../../domain/test-outcomes.js";export async function executeEngineTasks(e,t,n,i,o){const a=[];for(const r of t){e.observer?.taskStarted(r.task);const s=Date.now(),c=taskBeforeStartOutcome(r.task,e.signal,s,n);c!==null?a.push(c):r.preparationFailure!==null?a.push(terminalOutcome(r.task.id,"failed",s,emptyEvidence(),r.preparationFailure)):a.push(await i(r,s))}for(const[r,s]of a.entries())e.observer?.evidenceRecorded(t[r].task.id,s.evidence),e.observer?.taskFinished(s);return{evidence:{artifacts:a.flatMap(r=>r.evidence.artifacts),logs:a.flatMap(r=>r.evidence.logs),metrics:a.flatMap(r=>r.evidence.metrics),native:o},invocationId:e.invocationId,outcomes:a}}export function taskBeforeStartOutcome(e,t,n,i){return e.deadlineMs<=n||d(t.reason)?terminalOutcome(e.id,"failed",n,emptyEvidence(),taskTimeoutFailure(e.timeoutMs,i)):t.aborted?terminalOutcome(e.id,"cancelled",n,emptyEvidence(),{code:`${i.codePrefix}_CANCELLED`,message:`${i.label} task was cancelled before its process started`}):null}export function taskTimeoutFailure(e,t){return{code:`${t.codePrefix}_TASK_TIMEOUT`,details:{timeoutMs:e},message:`${t.label} task exceeded its ${e.toString()}ms timeout`}}export function managedProcessFailure(e,t,n=null){return e.terminationError===null?null:{classification:"infra",code:`${t.codePrefix}_PROCESS_TERMINATION_INCOMPLETE`,details:{cancelled:e.cancelled,...n===null?{}:{evidenceCollectionFailure:errorDetails(n)},outputLimitExceeded:e.outputLimitExceeded,timedOut:e.timedOut},message:`${t.label} managed-process termination was incomplete: ${e.terminationError.message}`,...e.terminationError.stack===void 0?{}:{stack:e.terminationError.stack}}}export function classifyManagedProcessResult(e,t,n,i){const o=r=>({failure:r,status:"failed"}),a=managedProcessFailure(e,i);return a!==null?o(a):e.timedOut||n?o(taskTimeoutFailure(t,i)):e.cancelled?{failure:{code:`${i.codePrefix}_CANCELLED`,message:`${i.label} task was cancelled`},status:"cancelled"}:e.outputLimitExceeded?o({code:`${i.codePrefix}_OUTPUT_LIMIT`,message:`${i.label} task exceeded the bounded stdout/stderr capture limit`}):e.error!==null?o({...failureFromError(e.error,`${i.codePrefix}_PROCESS_START_FAILED`),classification:"infra"}):null}export function processExitDetails(e){return{...e.exitCode===null?{}:{exitCode:e.exitCode},...e.exitSignal===null?{}:{exitSignal:e.exitSignal}}}export function processExitFailure(e,t,n=null,i=t.label){return{failure:{code:`${t.codePrefix}_PROCESS_FAILED`,details:processExitDetails(e),message:n??(e.exitCode===null?`${i} process terminated by ${e.exitSignal??"an unknown signal"}`:`${i} process exited with code ${e.exitCode.toString()}`)},status:"failed"}}export function firstOutputLine(e){const t=e.split(/\r?\n/u).map(n=>n.trim()).find(Boolean);return t===void 0?null:t.slice(0,4e3)}export function terminalOutcome(e,t,n,i,o){const a=Date.now();return{durationMs:Math.max(0,a-n),evidence:i,failure:o,finishedAtMs:a,startedAtMs:n,status:t,taskId:e}}export function processOutputEvidence(e){return{artifacts:[],logs:[...e.stdout.length===0?[]:[{stream:"stdout",text:e.stdout}],...e.stderr.length===0?[]:[{stream:"stderr",text:e.stderr}]],metrics:[]}}export function emptyEvidence(){return{artifacts:[],logs:[],metrics:[]}}export function failureFromError(e,t){const n=toError(e);return{code:t,message:n.message,...n.stack===void 0?{}:{stack:n.stack}}}export function errorDetails(e){return{message:e.message,name:e.name,...e.stack===void 0?{}:{stack:e.stack}}}export function toError(e){return e instanceof Error?e:new Error(String(e))}
@@ -1 +1 @@
1
- import{makeTempDirAsync as k}from"../../../process/files/temp-dir.js";import{classifyManagedProcessResult as O,terminalOutcome as g,emptyEvidence as b,failureFromError as E,processExitFailure as M,toError as w,executeEngineTasks as P,managedProcessFailure as F}from"../../adapters/execution.js";import{requireRootDirectory as U,requireAbsoluteWorkspacePath as K,requireConsumerFile as L,safeSegment as h}from"../../adapters/workspace.js";import f from"node:fs/promises";import T from"node:path";import{isEngineTaskDeadlineExceededReason as C,resolveEngineTaskInputs as V}from"../../api/index.js";import{createInvocationProcessEnvironment as j,runManagedProcess as $}from"../../../process/index.js";import{bundleK6Suite as Y,K6ConsumerCompilationError as B}from"./bundle.js";import{collectK6Evidence as z}from"./evidence.js";import{assertK6SpawnEnvironmentBudget as H,createK6RuntimeInputDocument as G,K6_RUNTIME_INPUT_FILE_ENV as J,K6_RUNTIME_INPUT_SHA256_ENV as W,mergeK6Environment as X,selectK6CustomEnvironment as R,verifyOwnedK6RuntimeInput as Q,writeOwnedK6RuntimeInput as Z}from"./input.js";import{K6_RUNTIME_INVOCATION_ID_ENV as q,K6_RUNTIME_TASK_ID_ENV as ee}from"../../api/k6-runtime-input.js";import{collectK6NativeSummaryArtifacts as te,K6_NATIVE_SUMMARY_DIRECTORY as re}from"./native-summary.js";import{engineEvidenceIdentity as ne}from"../../adapters/evidence-identity.js";const I={codePrefix:"K6",label:"k6"};class N extends Error{constructor(t){const r=w(t);super(r.message,{cause:r}),this.name="K6ConsumerPreparationError"}}export const k6ExecutorDependencies={removeOwnedDirectory:e=>f.rm(e,{force:!0,recursive:!0}),writeRuntimeInput:Z};export async function executeK6Engine(e,t,r,n=k6ExecutorDependencies){const i=await ie(e,t,n,r);return P(e,i.tasks,I,(a,s)=>oe(e,i,a,s),fe(e,i.installation))}async function ie(e,t,r,n){const i=e.profile,a=t.executable,s=await U(e.workspace.rootDirectory);K(e.workspace.temporaryDirectory,"temporary directory"),K(e.workspace.artifactDirectory,"artifact directory"),await f.mkdir(e.workspace.temporaryDirectory,{recursive:!0}),await f.mkdir(e.workspace.artifactDirectory,{recursive:!0});const o=await k(`k6-${h(e.invocationId)}-`,{parentDir:e.workspace.temporaryDirectory});n.defer(()=>r.removeOwnedDirectory(o));const c=await j(o),d=[];for(const[u,l]of e.tasks.entries()){const m=V(e,l),D=await k(`${u.toString().padStart(3,"0")}-${h(l.id)}-`,{parentDir:e.workspace.artifactDirectory}),p=T.join(o,`${u.toString().padStart(3,"0")}-${h(l.id)}`),S=T.join(p,"suite.js"),y=T.join(p,re);try{await f.mkdir(p,{mode:448,recursive:!1}),await f.mkdir(y,{mode:448,recursive:!1});const _=await A(()=>L(s,l.file,"k6 task file"));await A(()=>{de(m.supportModules),me(e,i,l.serviceName,m),R(m.environment)});const x=await r.writeRuntimeInput(p,G(e,l,m));await Y({configuration:m.configuration,entrypoints:t.authoringEntrypoints,nativeSummaryDirectory:y,outputFile:S,rootDirectory:s,signal:e.signal,sourceFile:_}),d.push({artifactDirectory:D,inputs:m,task:l,preparationFailure:null,prepared:{bundleFile:S,nativeSummaryDirectory:y,runtimeInput:x}})}catch(_){d.push({artifactDirectory:D,inputs:m,task:l,preparationFailure:pe(_),prepared:null})}}return Object.freeze({executable:a,installation:t,invocationEnvironment:c.environment,rootDirectory:s,tasks:Object.freeze(d)})}async function oe(e,t,r,n){const i=Math.max(0,r.task.deadlineMs-n);try{await Q(r.prepared.runtimeInput)}catch(u){return g(r.task.id,"failed",n,b(),{...E(u,"K6_INPUT_INVALID"),classification:"infra"})}const a=`${r.prepared.bundleFile}.summary.json`,s=le(e,t,r),o=await $({args:["run","--address","127.0.0.1:0","--no-color","--quiet","--summary-export",a,r.prepared.bundleFile],cwd:t.rootDirectory,displayLabel:"k6",env:s,executable:t.executable,signal:e.signal,timeoutMs:i});let c;try{c=await z({artifactDirectory:r.artifactDirectory,stderr:o.stderr,stdout:o.stdout,summaryFile:a});const u=await te(r.prepared.nativeSummaryDirectory,r.artifactDirectory);u.length>0&&(c={...c,evidence:{...c.evidence,artifacts:[...c.evidence.artifacts,...u]}})}catch(u){return g(r.task.id,"failed",n,{artifacts:[],logs:[...o.stdout.length>0?[{stream:"stdout",text:o.stdout}]:[],...o.stderr.length>0?[{stream:"stderr",text:o.stderr}]:[]],metrics:[]},k6EvidenceCollectionFailure(o,u))}const d=classifyK6ProcessResult(o,c.summary,c.summaryError,r.task.timeoutMs,C(e.signal.reason));return g(r.task.id,d.status,n,c.evidence,d.failure)}export function classifyK6ProcessResult(e,t,r,n,i){const a=O(e,n,i,I);if(a!==null)return a;const s=ce(e.stderr),o=ae(e.stderr);if(o!==null)return{failure:{code:"K6_SUMMARY_OUTPUT_INVALID",message:o,stack:e.stderr},status:"failed"};if(s!==null)return{failure:{code:"K6_RUNTIME_EXCEPTION",message:s,stack:e.stderr},status:"failed"};const c=ue(t);return c.length>0?{failure:{code:"K6_THRESHOLDS_FAILED",details:{thresholds:c},message:`k6 thresholds failed: ${c.join(", ")}`},status:"failed"}:e.exitCode!==0?M(e,I):r!==null?{failure:{...E(r,"K6_SUMMARY_INVALID"),classification:"infra"},status:"failed"}:{failure:null,status:"passed"}}export function k6EvidenceCollectionFailure(e,t){const r=w(t);return F(e,I,r)??{...E(r,"K6_EVIDENCE_FAILED"),classification:"infra"}}function ae(e){return e.match(/TESTKIT_K6_SUMMARY_OUTPUT_INVALID:\s*([^\n"]+)/u)?.[1]?.trim()??null}function ce(e){for(const t of e.split(/\r?\n/u)){if(!t.includes("level=error")||!t.includes("source=stacktrace")&&!t.includes('hint="script exception"'))continue;const r=t.match(/\bmsg="((?:\\.|[^"\\])*)"/u)?.[1];if(r===void 0)continue;const n=se(r).split(/\r?\n/u)[0]?.trim();if(!n)continue;return n.match(/^(?:Error|GoError|TypeError|ReferenceError|RangeError):\s*(.+)$/u)?.[1]?.trim()??n}return null}function se(e){try{const t=JSON.parse(`"${e}"`);if(typeof t=="string")return t}catch{}return e}function ue(e){if(e===null||!v(e.metrics))return[];const t=[];for(const[r,n]of Object.entries(e.metrics))if(!(!v(n)||!v(n.thresholds)))for(const[i,a]of Object.entries(n.thresholds))a===!0&&t.push(`${r}(${i})`);return t.sort()}function le(e,t,r){const n=r.task,i=X(t.invocationEnvironment,R(r.inputs.environment),{K6_NO_USAGE_REPORT:"true",TESTKIT_ACTIVE:"1",[J]:r.prepared.runtimeInput.file,[W]:r.prepared.runtimeInput.sha256,TESTKIT_INTERNAL_FILE_DEADLINE_MS:String(n.deadlineMs),TESTKIT_INTERNAL_FILE_TIMEOUT_SECONDS:String(Math.max(1,Math.ceil(n.timeoutMs/1e3))),[q]:e.invocationId,TESTKIT_LEASE_DIR:n.orchestration.lease.directory,TESTKIT_LEASE_ID:n.orchestration.lease.id,TESTKIT_MODE:n.orchestration.mode,...n.orchestration.runtimeId===void 0?{}:{TESTKIT_RUNTIME_ID:n.orchestration.runtimeId},TESTKIT_TEST_FILE:n.file,[ee]:n.id});return e.profile.id==="k6-http"&&Object.defineProperty(i,"BASE_URL",{configurable:!0,enumerable:!0,value:e.target.endpoints[n.serviceName],writable:!0}),H(i),Object.freeze(i)}function me(e,t,r,n){if(t.id==="k6-http"){const a=e.target.endpoints[r];if(typeof a!="string"||a.trim().length===0)throw new Error(`k6-http requires a published endpoint for service ${r}`);return}const i=n.environment.DATABASE_URL;if(typeof i!="string"||i.trim().length===0)throw new Error("k6-dal requires DATABASE_URL in its resolved task inputs")}function de(e){const t=Object.keys(e);if(t.length!==0)throw new Error(`k6 does not accept support modules (${t.join(", ")}); repository configuration must cross the engine boundary as validated JSON`)}function fe(e,t){return{...ne({engineId:"k6",installationKind:"direct-package",profile:e.profile,target:t.target}),packageName:t.targetPackage.name,packageVersion:t.targetPackage.version,runtimeObservedVersion:t.engineVersion}}function pe(e){return{...E(e,"K6_PREPARE_FAILED"),classification:e instanceof N||e instanceof B?"product":"infra"}}async function A(e){try{return await e()}catch(t){throw new N(t)}}function v(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}
1
+ import{makeTempDirAsync as S}from"../../../process/files/temp-dir.js";import{classifyManagedProcessResult as O,terminalOutcome as y,emptyEvidence as M,failureFromError as I,processExitDetails as R,processExitFailure as P,toError as w,executeEngineTasks as F,managedProcessFailure as U}from"../../adapters/execution.js";import{requireRootDirectory as L,requireAbsoluteWorkspacePath as K,requireConsumerFile as C,safeSegment as T}from"../../adapters/workspace.js";import m from"node:fs/promises";import v from"node:path";import{isEngineTaskDeadlineExceededReason as V,resolveEngineTaskInputs as j}from"../../api/index.js";import{createInvocationProcessEnvironment as $,runManagedProcess as Y}from"../../../process/index.js";import{bundleK6Suite as B,K6ConsumerCompilationError as H}from"./bundle.js";import{collectK6Evidence as z}from"./evidence.js";import{assertK6SpawnEnvironmentBudget as G,createK6RuntimeInputDocument as J,K6_RUNTIME_INPUT_FILE_ENV as W,K6_RUNTIME_INPUT_SHA256_ENV as X,mergeK6Environment as Q,selectK6CustomEnvironment as N,verifyOwnedK6RuntimeInput as Z,writeOwnedK6RuntimeInput as q}from"./input.js";import{K6_RUNTIME_INVOCATION_ID_ENV as ee,K6_RUNTIME_TASK_ID_ENV as te}from"../../api/k6-runtime-input.js";import{collectK6NativeSummaryArtifacts as re,K6_NATIVE_SUMMARY_DIRECTORY as ne}from"./native-summary.js";import{engineEvidenceIdentity as ie}from"../../adapters/evidence-identity.js";const g={codePrefix:"K6",label:"k6"},oe=/^Uncaught testkit suite error during (setup|exec): /u;class A extends Error{constructor(t){const r=w(t);super(r.message,{cause:r}),this.name="K6ConsumerPreparationError"}}export const k6ExecutorDependencies={removeOwnedDirectory:e=>m.rm(e,{force:!0,recursive:!0}),writeRuntimeInput:q};export async function executeK6Engine(e,t,r,n=k6ExecutorDependencies){const i=await ae(e,t,n,r);return F(e,i.tasks,g,(a,u)=>ce(e,i,a,u),Ie(e,i.installation))}async function ae(e,t,r,n){const i=e.profile,a=t.executable,u=await L(e.workspace.rootDirectory);K(e.workspace.temporaryDirectory,"temporary directory"),K(e.workspace.artifactDirectory,"artifact directory"),await m.mkdir(e.workspace.temporaryDirectory,{recursive:!0}),await m.mkdir(e.workspace.artifactDirectory,{recursive:!0});const o=await S(`k6-${T(e.invocationId)}-`,{parentDir:e.workspace.temporaryDirectory});n.defer(()=>r.removeOwnedDirectory(o));const c=await $(o),s=[];for(const[l,d]of e.tasks.entries()){const f=j(e,d),D=await S(`${l.toString().padStart(3,"0")}-${T(d.id)}-`,{parentDir:e.workspace.artifactDirectory}),E=v.join(o,`${l.toString().padStart(3,"0")}-${T(d.id)}`),k=v.join(E,"suite.js"),_=v.join(E,ne);try{await m.mkdir(E,{mode:448,recursive:!1}),await m.mkdir(_,{mode:448,recursive:!1});const h=await x(()=>C(u,d.file,"k6 task file"));await x(()=>{Ee(f.supportModules),pe(e,i,d.serviceName,f),N(f.environment)});const b=await r.writeRuntimeInput(E,J(e,d,f));await B({configuration:f.configuration,entrypoints:t.authoringEntrypoints,nativeSummaryDirectory:_,outputFile:k,rootDirectory:u,signal:e.signal,sourceFile:h}),s.push({artifactDirectory:D,inputs:f,task:d,preparationFailure:null,prepared:{bundleFile:k,nativeSummaryDirectory:_,runtimeInput:b}})}catch(h){s.push({artifactDirectory:D,inputs:f,task:d,preparationFailure:ge(h),prepared:null})}}return Object.freeze({executable:a,installation:t,invocationEnvironment:c.environment,rootDirectory:u,tasks:Object.freeze(s)})}async function ce(e,t,r,n){const i=Math.max(0,r.task.deadlineMs-n);try{await Z(r.prepared.runtimeInput)}catch(l){return y(r.task.id,"failed",n,M(),{...I(l,"K6_INPUT_INVALID"),classification:"infra"})}const a=`${r.prepared.bundleFile}.summary.json`,u=me(e,t,r),o=await Y({args:["run","--address","127.0.0.1:0","--no-color","--quiet","--summary-export",a,r.prepared.bundleFile],cwd:t.rootDirectory,displayLabel:"k6",env:u,executable:t.executable,signal:e.signal,timeoutMs:i});let c;try{c=await z({artifactDirectory:r.artifactDirectory,stderr:o.stderr,stdout:o.stdout,summaryFile:a});const l=await re(r.prepared.nativeSummaryDirectory,r.artifactDirectory);l.length>0&&(c={...c,evidence:{...c.evidence,artifacts:[...c.evidence.artifacts,...l]}})}catch(l){return y(r.task.id,"failed",n,{artifacts:[],logs:[...o.stdout.length>0?[{stream:"stdout",text:o.stdout}]:[],...o.stderr.length>0?[{stream:"stderr",text:o.stderr}]:[]],metrics:[]},k6EvidenceCollectionFailure(o,l))}const s=classifyK6ProcessResult(o,c.summary,c.summaryError,r.task.timeoutMs,V(e.signal.reason));return y(r.task.id,s.status,n,c.evidence,s.failure)}export function classifyK6ProcessResult(e,t,r,n,i){const a=O(e,n,i,g);if(a!==null)return a;const u=ue(e.stderr),o=se(e.stderr);if(o!==null)return{failure:{code:"K6_SUMMARY_OUTPUT_INVALID",message:o,stack:e.stderr},status:"failed"};if(u!==null){const s=oe.exec(u)?.[1];return{failure:{code:"K6_RUNTIME_EXCEPTION",details:{...s===void 0?{}:{phase:s},...R(e),summaryRead:r===null},message:u,stack:e.stderr},status:"failed"}}const c=de(t);if(c.length>0){const s=fe(t);return{failure:{code:"K6_THRESHOLDS_FAILED",details:{...R(e),...s===null?{}:{failedChecks:s},thresholds:c},message:`k6 thresholds failed: ${c.join(", ")}`},status:"failed"}}return e.exitCode!==0?P(e,g):r!==null?{failure:{...I(r,"K6_SUMMARY_INVALID"),classification:"infra"},status:"failed"}:{failure:null,status:"passed"}}export function k6EvidenceCollectionFailure(e,t){const r=w(t);return U(e,g,r)??{...I(r,"K6_EVIDENCE_FAILED"),classification:"infra"}}function se(e){return e.match(/TESTKIT_K6_SUMMARY_OUTPUT_INVALID:\s*([^\n"]+)/u)?.[1]?.trim()??null}function ue(e){for(const t of e.split(/\r?\n/u)){if(!t.includes("level=error")||!t.includes("source=stacktrace")&&!t.includes('hint="script exception"'))continue;const r=t.match(/\bmsg="((?:\\.|[^"\\])*)"/u)?.[1];if(r===void 0)continue;const n=le(r).split(/\r?\n/u)[0]?.trim();if(!n)continue;return n.match(/^(?:Error|GoError|TypeError|ReferenceError|RangeError):\s*(.+)$/u)?.[1]?.trim()??n}return null}function le(e){try{const t=JSON.parse(`"${e}"`);if(typeof t=="string")return t}catch{}return e}function de(e){if(e===null||!p(e.metrics))return[];const t=[];for(const[r,n]of Object.entries(e.metrics))if(!(!p(n)||!p(n.thresholds)))for(const[i,a]of Object.entries(n.thresholds))a===!0&&t.push(`${r}(${i})`);return t.sort()}function fe(e){if(e===null||!p(e.metrics)||!p(e.metrics.checks))return null;const{fails:t}=e.metrics.checks;return typeof t=="number"&&Number.isSafeInteger(t)&&t>=0?t:null}function me(e,t,r){const n=r.task,i=Q(t.invocationEnvironment,N(r.inputs.environment),{K6_NO_USAGE_REPORT:"true",TESTKIT_ACTIVE:"1",[W]:r.prepared.runtimeInput.file,[X]:r.prepared.runtimeInput.sha256,TESTKIT_INTERNAL_FILE_DEADLINE_MS:String(n.deadlineMs),TESTKIT_INTERNAL_FILE_TIMEOUT_SECONDS:String(Math.max(1,Math.ceil(n.timeoutMs/1e3))),[ee]:e.invocationId,TESTKIT_LEASE_DIR:n.orchestration.lease.directory,TESTKIT_LEASE_ID:n.orchestration.lease.id,TESTKIT_MODE:n.orchestration.mode,...n.orchestration.runtimeId===void 0?{}:{TESTKIT_RUNTIME_ID:n.orchestration.runtimeId},TESTKIT_TEST_FILE:n.file,[te]:n.id});return e.profile.id==="k6-http"&&Object.defineProperty(i,"BASE_URL",{configurable:!0,enumerable:!0,value:e.target.endpoints[n.serviceName],writable:!0}),G(i),Object.freeze(i)}function pe(e,t,r,n){if(t.id==="k6-http"){const a=e.target.endpoints[r];if(typeof a!="string"||a.trim().length===0)throw new Error(`k6-http requires a published endpoint for service ${r}`);return}const i=n.environment.DATABASE_URL;if(typeof i!="string"||i.trim().length===0)throw new Error("k6-dal requires DATABASE_URL in its resolved task inputs")}function Ee(e){const t=Object.keys(e);if(t.length!==0)throw new Error(`k6 does not accept support modules (${t.join(", ")}); repository configuration must cross the engine boundary as validated JSON`)}function Ie(e,t){return{...ie({engineId:"k6",installationKind:"direct-package",profile:e.profile,target:t.target}),packageName:t.targetPackage.name,packageVersion:t.targetPackage.version,runtimeObservedVersion:t.engineVersion}}function ge(e){return{...I(e,"K6_PREPARE_FAILED"),classification:e instanceof A||e instanceof H?"product":"infra"}}async function x(e){try{return await e()}catch(t){throw new A(t)}}function p(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}
@@ -1 +1 @@
1
- import{check as S}from"k6";import N from"k6/execution";import{emitArtifact as b}from"./artifacts.js";import{areRowsSorted as z}from"./shared/ordering.js";const c=w();export const failureDetails={begin:startFailureCollection,flush:emitFailureCollectionArtifact};export function evaluateCheck(e,t,n,i=null){const r=s(t,"unnamed check"),u=S(e,{[r]:n});if(!u){const a=i?i():{kind:"runtime-check",key:r,title:r,checkName:r,phase:c.phase};recordFailureDetail(a)}return u}export function json(e,t){if(typeof e.body!="string")throw new TypeError("response body must be text to parse JSON");const n=JSON.parse(e.body);if(t&&!t(n))throw new TypeError("response JSON did not match the expected shape");return n}export function contains(e,t,n){const i=String(t);return e.some(r=>r[i]===n)}export function allMatch(e,t){return e.length>0&&e.every(t)}export function isSorted(e,t,n="asc"){return z(e,t,n)}export function startFailureCollection(e){c.phase=s(e,"exec"),c.detailsByKey=new Map}export function emitFailureCollectionArtifact(){const e=[...c.detailsByKey.values()].sort((t,n)=>t.key.localeCompare(n.key)).map(t=>({...t}));e.length>0&&b("failure-details",{phase:c.phase,failures:e},{kind:"testkit.failure-details",summary:`${String(e.length)} failure detail(s)`}),startFailureCollection(c.phase)}export function recordFailureDetail(e){const t=J(e);if(!t)return;const n=N.vu.metrics.tags.group;typeof n=="string"&&n.length>0&&(t.groupPath=n.replace(/^::/,"").split("::"),t.key=K(t.groupPath,t.key));const i=c.detailsByKey.get(t.key);if(!i){c.detailsByKey.set(t.key,t);return}i.count+=t.count}function w(){return{phase:"exec",detailsByKey:new Map}}function J(e){if(!f(e))return null;const t=s(o(e,"kind"),null),n=s(o(e,"key"),null),i=s(o(e,"title"),null);if(!t||!n||!i)return null;const r={kind:t,key:n,title:i,count:1},u=s(o(e,"checkName"),null);u&&(r.checkName=u);const a=s(o(e,"phase"),null);a&&(r.phase=a);const p=s(o(e,"message"),null);p&&(r.message=p);const m=o(e,"expected");m!==void 0&&(r.expected=m);const y=o(e,"actual");y!==void 0&&(r.actual=y);const h=s(o(e,"traceId"),null);h&&(r.traceId=h);const d=l(o(e,"request"));d&&(r.request=d);const k=l(o(e,"response"));k&&(r.response=k);const g=l(o(e,"location"));g&&(r.location=g);const x=s(o(e,"stack"),null);return x&&(r.stack=x),r}function K(e,t){return e.length===0?t:[...e,t].join(" > ")}function s(e,t){if(typeof e!="string")return t;const n=e.trim();return n.length>0?n:t}function l(e){if(!f(e))return null;try{const t=B(e);if(t===void 0)return null;const n=JSON.parse(t);return O(n)?n:null}catch{return null}}function o(e,t){return Reflect.get(e,t)}function f(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function O(e){return f(e)}function B(e){return JSON.stringify(e)}
1
+ import{check as b}from"k6";import S from"k6/execution";import{emitArtifact as z}from"./artifacts.js";import{areRowsSorted as J}from"./shared/ordering.js";const c=O();export const failureDetails={begin:startFailureCollection,flush:emitFailureCollectionArtifact};export function evaluateCheck(e,t,n,i=null){const r=s(t,"unnamed check"),a=b(e,{[r]:n});if(!a){const l=i?i():{kind:"runtime-check",key:r,title:r,checkName:r,phase:c.phase};recordFailureDetail(l)}return a}export function json(e,t){if(typeof e.body!="string")throw new TypeError("response body must be text to parse JSON");const n=JSON.parse(e.body);if(t&&!t(n))throw new TypeError("response JSON did not match the expected shape");return n}export function contains(e,t,n){const i=String(t);return e.some(r=>r[i]===n)}export function allMatch(e,t){return e.length>0&&e.every(t)}export function isSorted(e,t,n="asc"){return J(e,t,n)}export function startFailureCollection(e){c.phase=s(e,"exec"),c.detailsByName=new Map}export function emitFailureCollectionArtifact(){const e=[...c.detailsByName.values()].sort((t,n)=>t.key.localeCompare(n.key)||t.title.localeCompare(n.title)).map(t=>({...t}));e.length>0&&z("failure-details",{phase:c.phase,failures:e},{kind:"testkit.failure-details",summary:`${String(e.length)} failure detail(s)`}),startFailureCollection(c.phase)}export function recordFailureDetail(e){const t=w(e);if(!t)return;const n=S.vu.metrics.tags.group;typeof n=="string"&&n.length>0&&(t.groupPath=n.replace(/^::/,"").split("::"),t.key=C(t.groupPath,t.key));const i=JSON.stringify([t.key,t.labelled??null,t.title]),r=c.detailsByName.get(i);if(!r){c.detailsByName.set(i,t);return}r.count+=t.count}function O(){return{phase:"exec",detailsByName:new Map}}function w(e){if(!f(e))return null;const t=s(o(e,"kind"),null),n=s(o(e,"key"),null),i=s(o(e,"title"),null);if(!t||!n||!i)return null;const r={kind:t,key:n,title:i,count:1},a=o(e,"labelled");typeof a=="boolean"&&(r.labelled=a);const l=s(o(e,"checkName"),null);l&&(r.checkName=l);const p=s(o(e,"phase"),null);p&&(r.phase=p);const m=s(o(e,"message"),null);m&&(r.message=m);const d=o(e,"expected");d!==void 0&&(r.expected=d);const h=o(e,"actual");h!==void 0&&(r.actual=h);const y=s(o(e,"traceId"),null);y&&(r.traceId=y);const k=u(o(e,"request"));k&&(r.request=k);const g=u(o(e,"response"));g&&(r.response=g);const x=u(o(e,"location"));x&&(r.location=x);const N=s(o(e,"stack"),null);return N&&(r.stack=N),r}function C(e,t){return e.length===0?t:[...e,t].join(" > ")}function s(e,t){if(typeof e!="string")return t;const n=e.trim();return n.length>0?n:t}function u(e){if(!f(e))return null;try{const t=F(e);if(t===void 0)return null;const n=JSON.parse(t);return B(n)?n:null}catch{return null}}function o(e,t){return Reflect.get(e,t)}function f(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function B(e){return f(e)}function F(e){return JSON.stringify(e)}
@@ -4,8 +4,8 @@ export declare function expectStatusOneOf(response: RuntimeResponse, expectedVal
4
4
  export declare function expectNotStatus(response: RuntimeResponse, unexpected: number, label?: string | null): boolean;
5
5
  export declare function expectJson(response: RuntimeResponse, predicate: RuntimePredicate<unknown>, label?: string | null): boolean;
6
6
  export declare function expectJsonPath(response: RuntimeResponse, path: string, predicate: RuntimePredicate<unknown>, label?: string | null): boolean;
7
- export declare function expectErrorShape(response: RuntimeResponse, label?: string): boolean;
8
- export declare function expectErrorMessage(response: RuntimeResponse, label?: string): boolean;
7
+ export declare function expectErrorShape(response: RuntimeResponse, label?: string | null): boolean;
8
+ export declare function expectErrorMessage(response: RuntimeResponse, label?: string | null): boolean;
9
9
  export declare function expectResponse(response: RuntimeResponse, predicate: RuntimePredicate<RuntimeResponse>, label: string | null): boolean;
10
10
  export declare function expectValue<T>(value: T, predicate: RuntimePredicate<T>, label: string | null, options?: RuntimeAssertionOptions<T>): boolean;
11
11
  export declare function expectCondition(predicate: () => boolean, label: string | null, options?: RuntimeConditionAssertionOptions): boolean;
@@ -1 +1 @@
1
- import{evaluateCheck as l}from"./checks.js";import{extractErrorMessageFromBody as w,hasStandardErrorShape as N}from"./shared/error-body.js";import{buildValueAssertionDetail as y,formatAssertionValue as d,normalizeErrorValue as J,resolveActual as q,resolveConditionActual as A,resolveConditionFailureMessage as F,resolveFailureMessage as P}from"./shared/assertion-details.js";import{safeJson as $,summarizeHttpTrace as c,toBodyPreview as E}from"./http.js";const R=String;export function expectStatus(t,e,r=null){return expectStatusOneOf(t,[e],r)}export function expectStatusOneOf(t,e,r=null){const n=j(e),o=s(r,V(n)),i=c(t),u=t.status;return l(t,o,a=>n.includes(a.status),()=>p({kind:"http-assertion",title:o,trace:i,expected:n.length===1?n[0]:n,actual:u,response:t,message:`${i?.method??"HTTP"} ${i?.path??"(unknown path)"} expected ${b(n)}, got ${String(u)}`}))}export function expectNotStatus(t,e,r=null){const n=s(r,`status is not ${String(e)}`),o=c(t),i=t.status;return l(t,n,u=>u.status!==e,()=>p({kind:"http-assertion",title:n,trace:o,expected:{not:e},actual:i,response:t,message:`${o?.method??"HTTP"} ${o?.path??"(unknown path)"} expected not ${String(e)}, got ${String(i)}`}))}export function expectJson(t,e,r=null){const n=s(r,"response JSON matches expectation"),o=c(t),i=$(t);return i.ok?l(i.value,n,u=>e(u),()=>({kind:"json-assertion",key:x(o,n),title:n,message:`${o?.method??"HTTP"} ${o?.path??"(unknown path)"} JSON assertion failed`,request:o?f(o):null,response:o?m(o):null,actual:k(i.value)})):S(t,o,n,"valid JSON response")}export function expectJsonPath(t,e,r,n=null){const o=s(n,`response JSON path ${e} matches expectation`),i=c(t),u=$(t);if(!u.ok)return S(t,i,o,`valid JSON containing ${e}`);const a=z(u.value,e);return l(a,o,T=>r(T),()=>({kind:"json-path-assertion",key:x(i,o),title:o,message:`${i?.method??"HTTP"} ${i?.path??"(unknown path)"} JSON path ${e} assertion failed`,expected:e,actual:k(a),request:i?f(i):null,response:i?m(i):null}))}export function expectErrorShape(t,e="response has standard error shape"){return expectJson(t,N,e)}export function expectErrorMessage(t,e="response includes extractable error message"){return expectJson(t,r=>w(r)!==null,e)}export function expectResponse(t,e,r){return l(t,s(r,"response matches expectation"),e)}export function expectValue(t,e,r,n={}){const o=s(r,"value matches expectation");return l(t,o,e,()=>y({kind:n.kind??"value-assertion",title:o,actual:q(t,n.actual),expected:n.expected,message:P(t,o,n.describeFailure)}))}export function expectCondition(t,e,r={}){const n=s(e,"condition holds");return l(null,n,()=>t(),()=>y({kind:r.kind??"condition-assertion",title:n,actual:A(r.actual),expected:r.expected,message:F(n,r.describeFailure)}))}export function expectRowCount(t,e,r=null){const n=e;if(!Number.isInteger(n)||n<0)throw new Error("expectRowCount requires a non-negative integer count");return expectValue(t,o=>o.length===n,s(r,`row count is ${String(n)}`),{actual:o=>o.length,describeFailure:o=>`expected ${String(n)} row(s), got ${String(o.length)}`,expected:n,kind:"row-count-assertion"})}export function expectSingleRow(t,e="query returns one row"){return expectRowCount(t,1,e)?t[0]??null:null}export function expectNoRows(t,e="query returns no rows"){return expectRowCount(t,0,e)}export function expectField(t,e,r,n=null){const o=String(e),u=(O(t)?t.at(0):t)?.[o],a=g(r)?void 0:r;return expectValue(u,H(r),s(n,`${o||"field"} matches expectation`),{actual:()=>u,describeFailure:()=>Array.isArray(t)&&t.length===0?`expected ${o||"field"}=${d(a)}, but query returned 0 rows`:`${o||"field"} expected ${d(a)}, got ${d(u)}`,expected:a,kind:"field-assertion"})}export function expectTruthyField(t,e,r=null){const n=String(e);return expectField(t,n,o=>!!o,s(r,`${n||"field"} is truthy`))}export function expectType(t,e,r=null){const n=C(e);return expectValue(t,o=>h(o)===n,s(r,`type is ${n}`),{actual:o=>h(o),describeFailure:o=>`expected type ${n}, got ${h(o)}`,expected:n,kind:"type-assertion"})}export function captureError(t){try{return t(),null}catch(e){return e}}export function expectError(t,e=null,r=null){const n=I(t)?captureError(t):t,o=g(e)?e:null;return expectValue(n,u=>u?o?o(u):!0:!1,s(typeof e=="string"&&r===null?e:r,"error is captured"),{actual:J(n),describeFailure:()=>"expected operation to throw",expected:"error",kind:"error-assertion"})}function S(t,e,r,n){return l(t,r,()=>!1,()=>p({kind:"http-json-assertion",title:r,trace:e,expected:n,actual:"invalid JSON response",response:t,message:`${e?.method??"HTTP"} ${e?.path??"(unknown path)"} did not return valid JSON`}))}function p({kind:t,title:e,trace:r,expected:n,actual:o,response:i,message:u}){return{kind:t,key:x(r,e),title:e,message:u,expected:n,actual:o,traceId:r?.id??void 0,request:r?f(r):null,response:r?m(r,i):null}}function f(t){return{id:t.id,requestId:t.requestId,method:t.method,path:t.path,url:t.url,headers:t.requestHeaders}}function m(t,e=null){const r=t.response?.status??e?.status??null;return{status:r===0?null:r,contentType:t.response?.contentType??null,bodyPreview:t.response?.bodyPreview??E(e),bodyTruncated:!!t.response?.bodyTruncated}}function x(t,e){return t?.path?`${t.method} ${t.path} > ${e}`:e}function V(t){return`status is ${b(t)}`}function b(t){return t.length===0?"expected":t.length===1?String(t[0]):t.map(e=>String(e)).join(" or ")}function j(t){const e=t.filter(r=>Number.isFinite(r));if(e.length===0)throw new Error("expectStatusOneOf requires at least one numeric expected status");return e}function z(t,e){const r=e.split(".").map(o=>o.trim()).filter(Boolean);let n=t;for(const o of r){if(n===null||typeof n!="object")return;n=Reflect.get(n,o)}return n}function k(t){if(t==null)return t;if(typeof t=="string")return t.length>240?`${t.slice(0,237)}...`:t;try{return(M(t)??"undefined").slice(0,240)}catch{return R(t)}}function H(t){return g(t)?t:e=>Object.is(e,t)}function h(t){return t===null?"null":Array.isArray(t)?"array":typeof t}function C(t){if(typeof t!="string")throw new Error("expectType requires a type name");const e=t.trim();if(!B(e))throw new Error("expectType requires a supported type name");return e}function B(t){return t==="array"||t==="bigint"||t==="boolean"||t==="function"||t==="null"||t==="number"||t==="object"||t==="string"||t==="symbol"||t==="undefined"}function s(t,e){const r=typeof t=="string"?t.trim():"";return r.length>0?r:e}function g(t){return typeof t=="function"}function I(t){return typeof t=="function"}function M(t){return JSON.stringify(t)}function O(t){return Array.isArray(t)}
1
+ import{evaluateCheck as a}from"./checks.js";import{extractErrorMessageFromBody as q,hasStandardErrorShape as F}from"./shared/error-body.js";import{buildValueAssertionDetail as S,formatAssertionValue as p,normalizeErrorValue as E,resolveActual as P,resolveConditionActual as R,resolveConditionFailureMessage as V,resolveFailureMessage as j}from"./shared/assertion-details.js";import{safeJson as b,summarizeHttpTrace as d,toBodyPreview as z}from"./http.js";const T="response JSON matches expectation",H=String;export function expectStatus(t,e,r=null){return expectStatusOneOf(t,[e],r)}export function expectStatusOneOf(t,e,r=null){const n=I(e),o=c(r,C(n)),i=d(t),u=t.status;return a(t,o.title,s=>n.includes(s.status),()=>m({...o,kind:"http-assertion",trace:i,expected:n.length===1?n[0]:n,actual:u,response:t,message:`${i?.method??"HTTP"} ${i?.path??"(unknown path)"} expected ${w(n)}, got ${String(u)}`}))}export function expectNotStatus(t,e,r=null){const n=c(r,`status is not ${String(e)}`),o=d(t),i=t.status;return a(t,n.title,u=>u.status!==e,()=>m({...n,kind:"http-assertion",trace:o,expected:{not:e},actual:i,response:t,message:`${o?.method??"HTTP"} ${o?.path??"(unknown path)"} expected not ${String(e)}, got ${String(i)}`}))}export function expectJson(t,e,r=null){return f(t,e,c(r,T))}function f(t,e,r){const n=d(t),o=b(t);return o.ok?a(o.value,r.title,i=>e(i),()=>({...r,kind:"json-assertion",key:g(n,r.title),message:`${n?.method??"HTTP"} ${n?.path??"(unknown path)"} JSON assertion failed`,request:n?x(n):null,response:n?h(n):null,actual:N(o.value)})):k(t,n,r,"valid JSON response")}export function expectJsonPath(t,e,r,n=null){const o=c(n,`response JSON path ${e} matches expectation`),i=d(t),u=b(t);if(!u.ok)return k(t,i,o,`valid JSON containing ${e}`);const s=O(u.value,e);return a(s,o.title,J=>r(J),()=>({...o,kind:"json-path-assertion",key:g(i,o.title),message:`${i?.method??"HTTP"} ${i?.path??"(unknown path)"} JSON path ${e} assertion failed`,expected:e,actual:N(s),request:i?x(i):null,response:i?h(i):null}))}export function expectErrorShape(t,e){return f(t,F,A(e,"response has standard error shape"))}export function expectErrorMessage(t,e){return f(t,r=>q(r)!==null,A(e,"response includes extractable error message"))}export function expectResponse(t,e,r){return a(t,l(r,"response matches expectation"),e)}export function expectValue(t,e,r,n={}){const o=l(r,"value matches expectation");return a(t,o,e,()=>S({kind:n.kind??"value-assertion",title:o,actual:P(t,n.actual),expected:n.expected,message:j(t,o,n.describeFailure)}))}export function expectCondition(t,e,r={}){const n=l(e,"condition holds");return a(null,n,()=>t(),()=>S({kind:r.kind??"condition-assertion",title:n,actual:R(r.actual),expected:r.expected,message:V(n,r.describeFailure)}))}export function expectRowCount(t,e,r=null){const n=e;if(!Number.isInteger(n)||n<0)throw new Error("expectRowCount requires a non-negative integer count");return expectValue(t,o=>o.length===n,l(r,`row count is ${String(n)}`),{actual:o=>o.length,describeFailure:o=>`expected ${String(n)} row(s), got ${String(o.length)}`,expected:n,kind:"row-count-assertion"})}export function expectSingleRow(t,e="query returns one row"){return expectRowCount(t,1,e)?t[0]??null:null}export function expectNoRows(t,e="query returns no rows"){return expectRowCount(t,0,e)}export function expectField(t,e,r,n=null){const o=String(e),u=(K(t)?t.at(0):t)?.[o],s=$(r)?void 0:r;return expectValue(u,B(r),l(n,`${o||"field"} matches expectation`),{actual:()=>u,describeFailure:()=>Array.isArray(t)&&t.length===0?`expected ${o||"field"}=${p(s)}, but query returned 0 rows`:`${o||"field"} expected ${p(s)}, got ${p(u)}`,expected:s,kind:"field-assertion"})}export function expectTruthyField(t,e,r=null){const n=String(e);return expectField(t,n,o=>!!o,l(r,`${n||"field"} is truthy`))}export function expectType(t,e,r=null){const n=M(e);return expectValue(t,o=>y(o)===n,l(r,`type is ${n}`),{actual:o=>y(o),describeFailure:o=>`expected type ${n}, got ${y(o)}`,expected:n,kind:"type-assertion"})}export function captureError(t){try{return t(),null}catch(e){return e}}export function expectError(t,e=null,r=null){const n=L(t)?captureError(t):t,o=$(e)?e:null;return expectValue(n,u=>u?o?o(u):!0:!1,l(typeof e=="string"&&r===null?e:r,"error is captured"),{actual:E(n),describeFailure:()=>"expected operation to throw",expected:"error",kind:"error-assertion"})}function k(t,e,r,n){return a(t,r.title,()=>!1,()=>m({...r,kind:"http-json-assertion",trace:e,expected:n,actual:"invalid JSON response",response:t,message:`${e?.method??"HTTP"} ${e?.path??"(unknown path)"} did not return valid JSON`}))}function m({kind:t,labelled:e,title:r,trace:n,expected:o,actual:i,response:u,message:s}){return{kind:t,key:g(n,r),title:r,labelled:e,message:s,expected:o,actual:i,traceId:n?.id??void 0,request:n?x(n):null,response:n?h(n,u):null}}function x(t){return{id:t.id,requestId:t.requestId,method:t.method,path:t.path,url:t.url,headers:t.requestHeaders}}function h(t,e=null){const r=t.response?.status??e?.status??null;return{status:r===0?null:r,contentType:t.response?.contentType??null,bodyPreview:t.response?.bodyPreview??z(e),bodyTruncated:!!t.response?.bodyTruncated}}function g(t,e){return t?.path?`${t.method} ${t.path} > ${e}`:e}function C(t){return`status is ${w(t)}`}function w(t){return t.length===0?"expected":t.length===1?String(t[0]):t.map(e=>String(e)).join(" or ")}function I(t){const e=t.filter(r=>Number.isFinite(r));if(e.length===0)throw new Error("expectStatusOneOf requires at least one numeric expected status");return e}function O(t,e){const r=e.split(".").map(o=>o.trim()).filter(Boolean);let n=t;for(const o of r){if(n===null||typeof n!="object")return;n=Reflect.get(n,o)}return n}function N(t){if(t==null)return t;if(typeof t=="string")return t.length>240?`${t.slice(0,237)}...`:t;try{return(_(t)??"undefined").slice(0,240)}catch{return H(t)}}function B(t){return $(t)?t:e=>Object.is(e,t)}function y(t){return t===null?"null":Array.isArray(t)?"array":typeof t}function M(t){if(typeof t!="string")throw new Error("expectType requires a type name");const e=t.trim();if(!D(e))throw new Error("expectType requires a supported type name");return e}function D(t){return t==="array"||t==="bigint"||t==="boolean"||t==="function"||t==="null"||t==="number"||t==="object"||t==="string"||t==="symbol"||t==="undefined"}function l(t,e){const r=typeof t=="string"?t.trim():"";return r.length>0?r:e}function c(t,e){const r=l(t,"");return r===""?{labelled:!1,title:e}:{labelled:!0,title:r}}function A(t,e){return c(t,t===void 0?e:T)}function $(t){return typeof t=="function"}function L(t){return typeof t=="function"}function _(t){return JSON.stringify(t)}function K(t){return Array.isArray(t)}
@@ -200,6 +200,7 @@ export interface RuntimeFailureDetail {
200
200
  groupPath?: string[];
201
201
  key: string;
202
202
  kind: string;
203
+ labelled?: boolean;
203
204
  location?: Record<string, unknown>;
204
205
  message?: string;
205
206
  phase?: string;
@@ -216,6 +217,7 @@ export interface RuntimeFailureDetailInput {
216
217
  expected?: unknown;
217
218
  key: string;
218
219
  kind: string;
220
+ labelled?: boolean;
219
221
  location?: object | null;
220
222
  message?: string;
221
223
  phase?: string;
@@ -1,2 +1,2 @@
1
- import w from"node:fs/promises";import y from"node:path";import{evidenceFileAccess as R,readStableEvidenceFile as b,writeOwnedEvidenceFile as $}from"../../adapters/evidence-files.js";import{NODE_VITEST_DATA_BUDGET_FAILURE_DETAIL_KIND as F,NODE_VITEST_DATA_FAILURE_DETAILS_ARTIFACT_KIND as _,NODE_VITEST_DATA_FAILURE_DETAILS_ARTIFACT_NAME as V}from"../runtime/runtime-input.js";import{vitestTestFailureDetails as B}from"./failure-details.js";import{readNativeReport as L,requireNativeEvidenceJson as M}from"../../adapters/native-report.js";const z=16*1024*1024,N=1024;export const NODE_VITEST_DATA_ARTIFACT_MAX_BYTES=16*1024*1024,NODE_VITEST_DATA_EVIDENCE_MAX_BYTES=64*1024*1024;const C=".testkit-owned-evidence";export async function collectNodeVitestEvidence(t,e=R){await w.mkdir(t.artifactDirectory,{recursive:!0});const r=await w.realpath(t.artifactDirectory),s=y.join(r,C);await w.mkdir(s,{mode:448,recursive:!1});const n=[],a=[];t.stdout.length>0&&(a.push({stream:"stdout",text:t.stdout}),n.push(await k(r,"stdout.log","node-vitest-stdout",t.stdout))),t.stderr.length>0&&(a.push({stream:"stderr",text:t.stderr}),n.push(await k(r,"stderr.log","node-vitest-stderr",t.stderr)));const i=await J(t.reportFile,t.identity,t.rootDirectory,s,e);n.push(...i.artifacts);const f=t.profileId==="node-vitest-data"?await X(r,s,t.dataManifestFile,e):{artifacts:[],error:null};return n.push(...f.artifacts),{dataEvidenceError:f.error,evidence:{artifacts:n,logs:a,metrics:[],...i.summary===null?{}:{native:{vitest:i.summary}}},reportError:i.error,reportFailureMessage:i.failureMessage,reportSummary:i.summary}}async function J(t,e,r,s,n){try{const a=await I(t,n);if(!a.exists||a.contents===null)throw new Error("Vitest did not produce its Testkit native report");const i=L(a.value,e).result;if(!T(i)||!T(i.summary))throw new Error("Vitest native report must contain result and summary objects");const f=G(i.summary),c=B(i.failures,r);if(f.numFailedTests!==c.length)throw new Error("Vitest native report failure count does not match its summary");if(!Array.isArray(i.errors)||!i.errors.every(m=>typeof m=="string"))throw new Error("Vitest native report errors must contain strings");if(f.success&&i.errors.length>0)throw new Error("Vitest native report success conflicts with runtime errors");const u=await $(s,"testkit-vitest-report.json",a.contents),o=await S(u,"application/json","engine.testkit-vitest-report","testkit-vitest-report","Testkit native Vitest report"),d=await U(s,c);return{artifacts:[o,...d===null?[]:[d]],error:null,failureMessage:typeof c[0]?.message=="string"?c[0].message:i.errors.find(m=>m.trim().length>0)??null,summary:f}}catch(a){return{artifacts:[],error:D(a),failureMessage:null,summary:null}}}async function U(t,e){if(e.length===0)return null;const r={failures:e,phase:"exec"},s=await $(t,"vitest-failure-details.json",Buffer.from(`${JSON.stringify(r,null,2)}
2
- `,"utf8"));return{...await S(s,"application/json","testkit.failure-details","vitest-failure-details",`${e.length.toString()} failed Vitest test${e.length===1?"":"s"}`),data:r}}async function X(t,e,r,s){let n;try{n=await I(r,s)}catch(c){return{artifacts:[],error:D(c)}}if(!n.exists)return{artifacts:[],error:new Error("Data profile did not produce an artifact manifest")};let a;try{a=Y(n.value)}catch(c){return{artifacts:[],error:D(c)}}const i=[],f=new Set;try{if(n.contents===null)throw new Error("Data artifact manifest bytes are unavailable");let c=n.contents.byteLength;if(c>NODE_VITEST_DATA_EVIDENCE_MAX_BYTES)throw new Error(`Data evidence exceeds its ${NODE_VITEST_DATA_EVIDENCE_MAX_BYTES.toString()} byte aggregate limit`);for(const[o,d]of a.artifacts.entries()){const m=await K(t,e,d,o,NODE_VITEST_DATA_EVIDENCE_MAX_BYTES-c,s),{artifact:l}=m,p=`${l.kind}\0${l.name}\0${m.sourcePath}`;if(f.has(p))throw new Error(`Data artifact manifest contains duplicate artifact ${l.name}`);f.add(p);const E=q(l.metadata?.sizeBytes,`Data artifact manifest artifacts[${o.toString()}]`);c+=E,i.push(l)}const u=await $(e,"manifest.json",n.contents);i.push(await S(u,"application/json","engine.data-manifest","data-artifact-manifest",`${i.length.toString()} data artifact(s)`))}catch(c){return{artifacts:i,error:D(c)}}return{artifacts:i,error:null}}function Y(t){if(!T(t))throw new Error("Data artifact manifest must contain an object");if(t.schemaVersion!==1)throw new Error("Data artifact manifest schemaVersion must be 1");if(!Array.isArray(t.artifacts))throw new Error("Data artifact manifest artifacts must be an array");if(t.artifacts.length===0)throw new Error("Data artifact manifest must contain at least one artifact");if(t.artifacts.length>N)throw new Error(`Data artifact manifest exceeds the ${N.toString()} artifact limit`);return{artifacts:t.artifacts,schemaVersion:t.schemaVersion}}async function K(t,e,r,s,n,a){const i=`Data artifact manifest artifacts[${s.toString()}]`;if(!T(r))throw new Error(`${i} must contain an object`);const f=g(r.kind,`${i}.kind`),c=g(r.name,`${i}.name`),u=g(r.path,`${i}.path`),o=await H(t,u,`${i}.path`),d=j(r.contentType,`${i}.contentType`),m=j(r.summary,`${i}.summary`),l=r.metadata===void 0?void 0:A(r.metadata,`${i}.metadata`),{contents:p}=await b(o,NODE_VITEST_DATA_ARTIFACT_MAX_BYTES,i,a);if(p.byteLength>n)throw new Error(`Data evidence exceeds its ${NODE_VITEST_DATA_EVIDENCE_MAX_BYTES.toString()} byte aggregate limit`);const E=W(f,c,d,p,i),O=await $(e,`${s.toString().padStart(4,"0")}-${Q(c)}${Z(o)}`,p);return{artifact:{kind:f,name:c,path:O,...d===void 0?{}:{contentType:d},...E===void 0?{}:{data:E},...m===void 0?{}:{summary:m},metadata:{...l??{},sizeBytes:p.byteLength}},sourcePath:o}}function W(t,e,r,s,n){if(t!==_)return;if(e!==V)throw new Error(`${n}.name must be ${JSON.stringify(V)} for ${_}`);if(r!=="application/json")throw new Error(`${n}.contentType must be "application/json" for ${_}`);const a=v(s,n),i=A(a,n);if(x(i,["failures","phase"],n),i.phase!=="exec")throw new Error(`${n}.phase must be "exec"`);if(!Array.isArray(i.failures)||i.failures.length===0)throw new Error(`${n}.failures must be a non-empty array`);for(const[f,c]of i.failures.entries()){const u=`${n}.failures[${f.toString()}]`,o=A(c,u);if(x(o,["actual","checkName","count","expected","groupPath","key","kind","message","title"],u),o.kind!==F)throw new Error(`${u}.kind must be ${JSON.stringify(F)}`);if(o.count!==1)throw new Error(`${u}.count must be 1`);const d=g(o.checkName,`${u}.checkName`),m=g(o.key,`${u}.key`),l=g(o.title,`${u}.title`);if(g(o.message,`${u}.message`),!Array.isArray(o.groupPath)||o.groupPath.length!==1||typeof o.groupPath[0]!="string"||o.groupPath[0].trim().length===0)throw new Error(`${u}.groupPath must contain one scenario name`);if(m!==`${o.groupPath[0]} > ${l}`)throw new Error(`${u}.key does not match its scenario and title`);if(!l.includes(`"${d}"`))throw new Error(`${u}.title does not identify its checkName`)}return i}async function k(t,e,r,s){const n=y.join(t,e);return await w.writeFile(n,s,"utf8"),S(n,"text/plain; charset=utf-8","engine.output",r,et(s)??"captured output")}async function S(t,e,r,s,n){const a=await w.stat(t);if(!a.isFile())throw new Error(`${s} artifact is not a regular file`);return{contentType:e,kind:r,metadata:{sizeBytes:a.size},name:s,path:t,summary:n}}async function I(t,e){let r;try{({contents:r}=await b(t,z,t,e))}catch(n){if(nt(n)&&n.code==="ENOENT")return{contents:null,exists:!1,value:null};throw n}const s=v(r,t);return{contents:r,exists:!0,value:s}}function v(t,e){const r=JSON.parse(t.toString("utf8"));return M(r,e),r}function G(t){const e={numFailedTests:h(t.numFailedTests,"Vitest native report.summary.numFailedTests"),numPassedTests:h(t.numPassedTests,"Vitest native report.summary.numPassedTests"),numPendingTests:h(t.numPendingTests,"Vitest native report.summary.numPendingTests"),numTodoTests:h(t.numTodoTests,"Vitest native report.summary.numTodoTests"),numTotalTests:h(t.numTotalTests,"Vitest native report.summary.numTotalTests"),numFailedTestSuites:h(t.numFailedTestSuites,"Vitest native report.summary.numFailedTestSuites"),numPassedTestSuites:h(t.numPassedTestSuites,"Vitest native report.summary.numPassedTestSuites"),numPendingTestSuites:h(t.numPendingTestSuites,"Vitest native report.summary.numPendingTestSuites"),numTotalTestSuites:h(t.numTotalTestSuites,"Vitest native report.summary.numTotalTestSuites"),success:tt(t.success,"Vitest native report.summary.success")};if(e.numTotalTests!==e.numFailedTests+e.numPassedTests+e.numPendingTests+e.numTodoTests||e.numTotalTestSuites!==e.numFailedTestSuites+e.numPassedTestSuites+e.numPendingTestSuites||e.success&&(e.numFailedTests>0||e.numFailedTestSuites>0||e.numTotalTestSuites===0))throw new Error("Vitest native report summary contains inconsistent counts or success");return e}async function H(t,e,r){const s=y.isAbsolute(e)?e:y.resolve(t,e),n=await w.lstat(s);if(!n.isFile()||n.isSymbolicLink())throw new Error(`${r} must be a regular non-symbolic-link file`);const a=await w.realpath(s);if(!rt(t,a))throw new Error(`${r} must stay within the task artifact directory`);return a}function Q(t){const e=t.trim().toLowerCase().replace(/[^a-z0-9._-]+/gu,"-").replace(/^-+|-+$/gu,"").slice(0,80);return e.length===0?"artifact":e}function Z(t){const e=y.extname(t).toLowerCase();return/^\.[a-z0-9]{1,12}$/u.test(e)?e:".bin"}function q(t,e){if(typeof t!="number"||!Number.isSafeInteger(t)||t<0)throw new Error(`${e} did not produce a valid sizeBytes identity`);return t}function A(t,e){if(!T(t))throw new Error(`${e} must contain an object`);const r={};for(const[s,n]of Object.entries(t))r[s]=P(n,`${e}.${s}`);return r}function x(t,e,r){const s=Object.keys(t).sort(),n=[...e].sort();if(s.length!==n.length||s.some((a,i)=>a!==n[i]))throw new Error(`${r} must contain exactly ${n.join(", ")}`)}function P(t,e){if(t===null||typeof t=="boolean"||typeof t=="string")return t;if(typeof t=="number"){if(!Number.isFinite(t))throw new Error(`${e} must be finite`);return t}return Array.isArray(t)?t.map((r,s)=>P(r,`${e}[${s.toString()}]`)):A(t,e)}function j(t,e){if(t!==void 0){if(typeof t!="string")throw new Error(`${e} must be a string`);return t}}function g(t,e){if(typeof t!="string"||t.trim().length===0)throw new Error(`${e} must be a non-empty string`);return t}function h(t,e){if(typeof t!="number"||!Number.isSafeInteger(t)||t<0)throw new Error(`${e} must be a non-negative safe integer`);return t}function tt(t,e){if(typeof t!="boolean")throw new Error(`${e} must be boolean`);return t}function et(t){return t.split(/\r?\n/u).map(e=>e.trim()).find(Boolean)??null}function rt(t,e){const r=y.relative(t,e);return r===""||!r.startsWith("..")&&!y.isAbsolute(r)}function T(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function nt(t){return t instanceof Error}function D(t){return t instanceof Error?t:new Error(String(t))}
1
+ import w from"node:fs/promises";import y from"node:path";import{evidenceFileAccess as B,readStableEvidenceFile as V,writeOwnedEvidenceFile as $}from"../../adapters/evidence-files.js";import{NODE_VITEST_DATA_BUDGET_FAILURE_DETAIL_KIND as b,NODE_VITEST_DATA_FAILURE_DETAILS_ARTIFACT_KIND as _,NODE_VITEST_DATA_FAILURE_DETAILS_ARTIFACT_NAME as F}from"../runtime/runtime-input.js";import{vitestErrorDetails as L,vitestTestFailureDetails as M}from"./failure-details.js";import{readNativeReport as z,requireNativeEvidenceJson as C}from"../../adapters/native-report.js";const J=16*1024*1024,N=1024;export const NODE_VITEST_DATA_ARTIFACT_MAX_BYTES=16*1024*1024,NODE_VITEST_DATA_EVIDENCE_MAX_BYTES=64*1024*1024;const U=".testkit-owned-evidence";export async function collectNodeVitestEvidence(t,e=B){await w.mkdir(t.artifactDirectory,{recursive:!0});const r=await w.realpath(t.artifactDirectory),s=y.join(r,U);await w.mkdir(s,{mode:448,recursive:!1});const n=[],a=[];t.stdout.length>0&&(a.push({stream:"stdout",text:t.stdout}),n.push(await I(r,"stdout.log","node-vitest-stdout",t.stdout))),t.stderr.length>0&&(a.push({stream:"stderr",text:t.stderr}),n.push(await I(r,"stderr.log","node-vitest-stderr",t.stderr)));const i=await X(t.reportFile,t.identity,t.rootDirectory,s,e);n.push(...i.artifacts);const f=t.profileId==="node-vitest-data"?await K(r,s,t.dataManifestFile,e):{artifacts:[],error:null};return n.push(...f.artifacts),{dataEvidenceError:f.error,evidence:{artifacts:n,logs:a,metrics:[],...i.summary===null?{}:{native:{vitest:i.summary}}},reportError:i.error,reportFailureMessage:i.failureMessage,reportSummary:i.summary}}async function X(t,e,r,s,n){try{const a=await v(t,n);if(!a.exists||a.contents===null)throw new Error("Vitest did not produce its Testkit native report");const i=z(a.value,e).result;if(!T(i)||!T(i.summary))throw new Error("Vitest native report must contain result and summary objects");const f=Q(i.summary),c=M(i.failures,r);if(f.numFailedTests!==c.length)throw new Error("Vitest native report failure count does not match its summary");if(!Array.isArray(i.errors)||!i.errors.every(m=>typeof m=="string"))throw new Error("Vitest native report errors must contain strings");if(f.success&&i.errors.length>0)throw new Error("Vitest native report success conflicts with runtime errors");const u=await $(s,"testkit-vitest-report.json",a.contents),o=await S(u,"application/json","engine.testkit-vitest-report","testkit-vitest-report","Testkit native Vitest report"),d=await Y(s,c,L(i.errors));return{artifacts:[o,...d===null?[]:[d]],error:null,failureMessage:typeof c[0]?.message=="string"?c[0].message:i.errors.find(m=>m.trim().length>0)??null,summary:f}}catch(a){return{artifacts:[],error:D(a),failureMessage:null,summary:null}}}async function Y(t,e,r){if(e.length+r.length===0)return null;const s={failures:[...e,...r],phase:"exec"},n=await $(t,"vitest-failure-details.json",Buffer.from(`${JSON.stringify(s,null,2)}
2
+ `,"utf8"));return{...await S(n,"application/json","testkit.failure-details","vitest-failure-details",[...e.length===0?[]:[k(e.length,"failed Vitest test")],...r.length===0?[]:[`${k(r.length,"Vitest error")} outside a test`]].join(" and ")),data:s}}function k(t,e){return`${t.toString()} ${e}${t===1?"":"s"}`}async function K(t,e,r,s){let n;try{n=await v(r,s)}catch(c){return{artifacts:[],error:D(c)}}if(!n.exists)return{artifacts:[],error:new Error("Data profile did not produce an artifact manifest")};let a;try{a=W(n.value)}catch(c){return{artifacts:[],error:D(c)}}const i=[],f=new Set;try{if(n.contents===null)throw new Error("Data artifact manifest bytes are unavailable");let c=n.contents.byteLength;if(c>NODE_VITEST_DATA_EVIDENCE_MAX_BYTES)throw new Error(`Data evidence exceeds its ${NODE_VITEST_DATA_EVIDENCE_MAX_BYTES.toString()} byte aggregate limit`);for(const[o,d]of a.artifacts.entries()){const m=await G(t,e,d,o,NODE_VITEST_DATA_EVIDENCE_MAX_BYTES-c,s),{artifact:l}=m,p=`${l.kind}\0${l.name}\0${m.sourcePath}`;if(f.has(p))throw new Error(`Data artifact manifest contains duplicate artifact ${l.name}`);f.add(p);const E=et(l.metadata?.sizeBytes,`Data artifact manifest artifacts[${o.toString()}]`);c+=E,i.push(l)}const u=await $(e,"manifest.json",n.contents);i.push(await S(u,"application/json","engine.data-manifest","data-artifact-manifest",`${i.length.toString()} data artifact(s)`))}catch(c){return{artifacts:i,error:D(c)}}return{artifacts:i,error:null}}function W(t){if(!T(t))throw new Error("Data artifact manifest must contain an object");if(t.schemaVersion!==1)throw new Error("Data artifact manifest schemaVersion must be 1");if(!Array.isArray(t.artifacts))throw new Error("Data artifact manifest artifacts must be an array");if(t.artifacts.length===0)throw new Error("Data artifact manifest must contain at least one artifact");if(t.artifacts.length>N)throw new Error(`Data artifact manifest exceeds the ${N.toString()} artifact limit`);return{artifacts:t.artifacts,schemaVersion:t.schemaVersion}}async function G(t,e,r,s,n,a){const i=`Data artifact manifest artifacts[${s.toString()}]`;if(!T(r))throw new Error(`${i} must contain an object`);const f=g(r.kind,`${i}.kind`),c=g(r.name,`${i}.name`),u=g(r.path,`${i}.path`),o=await Z(t,u,`${i}.path`),d=O(r.contentType,`${i}.contentType`),m=O(r.summary,`${i}.summary`),l=r.metadata===void 0?void 0:A(r.metadata,`${i}.metadata`),{contents:p}=await V(o,NODE_VITEST_DATA_ARTIFACT_MAX_BYTES,i,a);if(p.byteLength>n)throw new Error(`Data evidence exceeds its ${NODE_VITEST_DATA_EVIDENCE_MAX_BYTES.toString()} byte aggregate limit`);const E=H(f,c,d,p,i),R=await $(e,`${s.toString().padStart(4,"0")}-${q(c)}${tt(o)}`,p);return{artifact:{kind:f,name:c,path:R,...d===void 0?{}:{contentType:d},...E===void 0?{}:{data:E},...m===void 0?{}:{summary:m},metadata:{...l??{},sizeBytes:p.byteLength}},sourcePath:o}}function H(t,e,r,s,n){if(t!==_)return;if(e!==F)throw new Error(`${n}.name must be ${JSON.stringify(F)} for ${_}`);if(r!=="application/json")throw new Error(`${n}.contentType must be "application/json" for ${_}`);const a=x(s,n),i=A(a,n);if(P(i,["failures","phase"],n),i.phase!=="exec")throw new Error(`${n}.phase must be "exec"`);if(!Array.isArray(i.failures)||i.failures.length===0)throw new Error(`${n}.failures must be a non-empty array`);for(const[f,c]of i.failures.entries()){const u=`${n}.failures[${f.toString()}]`,o=A(c,u);if(P(o,["actual","checkName","count","expected","groupPath","key","kind","message","title"],u),o.kind!==b)throw new Error(`${u}.kind must be ${JSON.stringify(b)}`);if(o.count!==1)throw new Error(`${u}.count must be 1`);const d=g(o.checkName,`${u}.checkName`),m=g(o.key,`${u}.key`),l=g(o.title,`${u}.title`);if(g(o.message,`${u}.message`),!Array.isArray(o.groupPath)||o.groupPath.length!==1||typeof o.groupPath[0]!="string"||o.groupPath[0].trim().length===0)throw new Error(`${u}.groupPath must contain one scenario name`);if(m!==`${o.groupPath[0]} > ${l}`)throw new Error(`${u}.key does not match its scenario and title`);if(!l.includes(`"${d}"`))throw new Error(`${u}.title does not identify its checkName`)}return i}async function I(t,e,r,s){const n=y.join(t,e);return await w.writeFile(n,s,"utf8"),S(n,"text/plain; charset=utf-8","engine.output",r,nt(s)??"captured output")}async function S(t,e,r,s,n){const a=await w.stat(t);if(!a.isFile())throw new Error(`${s} artifact is not a regular file`);return{contentType:e,kind:r,metadata:{sizeBytes:a.size},name:s,path:t,summary:n}}async function v(t,e){let r;try{({contents:r}=await V(t,J,t,e))}catch(n){if(st(n)&&n.code==="ENOENT")return{contents:null,exists:!1,value:null};throw n}const s=x(r,t);return{contents:r,exists:!0,value:s}}function x(t,e){const r=JSON.parse(t.toString("utf8"));return C(r,e),r}function Q(t){const e={numFailedTests:h(t.numFailedTests,"Vitest native report.summary.numFailedTests"),numPassedTests:h(t.numPassedTests,"Vitest native report.summary.numPassedTests"),numPendingTests:h(t.numPendingTests,"Vitest native report.summary.numPendingTests"),numTodoTests:h(t.numTodoTests,"Vitest native report.summary.numTodoTests"),numTotalTests:h(t.numTotalTests,"Vitest native report.summary.numTotalTests"),numFailedTestSuites:h(t.numFailedTestSuites,"Vitest native report.summary.numFailedTestSuites"),numPassedTestSuites:h(t.numPassedTestSuites,"Vitest native report.summary.numPassedTestSuites"),numPendingTestSuites:h(t.numPendingTestSuites,"Vitest native report.summary.numPendingTestSuites"),numTotalTestSuites:h(t.numTotalTestSuites,"Vitest native report.summary.numTotalTestSuites"),success:rt(t.success,"Vitest native report.summary.success")};if(e.numTotalTests!==e.numFailedTests+e.numPassedTests+e.numPendingTests+e.numTodoTests||e.numTotalTestSuites!==e.numFailedTestSuites+e.numPassedTestSuites+e.numPendingTestSuites||e.success&&(e.numFailedTests>0||e.numFailedTestSuites>0||e.numTotalTestSuites===0))throw new Error("Vitest native report summary contains inconsistent counts or success");return e}async function Z(t,e,r){const s=y.isAbsolute(e)?e:y.resolve(t,e),n=await w.lstat(s);if(!n.isFile()||n.isSymbolicLink())throw new Error(`${r} must be a regular non-symbolic-link file`);const a=await w.realpath(s);if(!it(t,a))throw new Error(`${r} must stay within the task artifact directory`);return a}function q(t){const e=t.trim().toLowerCase().replace(/[^a-z0-9._-]+/gu,"-").replace(/^-+|-+$/gu,"").slice(0,80);return e.length===0?"artifact":e}function tt(t){const e=y.extname(t).toLowerCase();return/^\.[a-z0-9]{1,12}$/u.test(e)?e:".bin"}function et(t,e){if(typeof t!="number"||!Number.isSafeInteger(t)||t<0)throw new Error(`${e} did not produce a valid sizeBytes identity`);return t}function A(t,e){if(!T(t))throw new Error(`${e} must contain an object`);const r={};for(const[s,n]of Object.entries(t))r[s]=j(n,`${e}.${s}`);return r}function P(t,e,r){const s=Object.keys(t).sort(),n=[...e].sort();if(s.length!==n.length||s.some((a,i)=>a!==n[i]))throw new Error(`${r} must contain exactly ${n.join(", ")}`)}function j(t,e){if(t===null||typeof t=="boolean"||typeof t=="string")return t;if(typeof t=="number"){if(!Number.isFinite(t))throw new Error(`${e} must be finite`);return t}return Array.isArray(t)?t.map((r,s)=>j(r,`${e}[${s.toString()}]`)):A(t,e)}function O(t,e){if(t!==void 0){if(typeof t!="string")throw new Error(`${e} must be a string`);return t}}function g(t,e){if(typeof t!="string"||t.trim().length===0)throw new Error(`${e} must be a non-empty string`);return t}function h(t,e){if(typeof t!="number"||!Number.isSafeInteger(t)||t<0)throw new Error(`${e} must be a non-negative safe integer`);return t}function rt(t,e){if(typeof t!="boolean")throw new Error(`${e} must be boolean`);return t}function nt(t){return t.split(/\r?\n/u).map(e=>e.trim()).find(Boolean)??null}function it(t,e){const r=y.relative(t,e);return r===""||!r.startsWith("..")&&!y.isAbsolute(r)}function T(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function st(t){return t instanceof Error}function D(t){return t instanceof Error?t:new Error(String(t))}
@@ -1,2 +1,3 @@
1
1
  import type { EngineJsonObject } from "../../api/index.js";
2
2
  export declare function vitestTestFailureDetails(value: unknown, rootDirectory: string): EngineJsonObject[];
3
+ export declare function vitestErrorDetails(errors: readonly string[]): EngineJsonObject[];
@@ -1,2 +1,2 @@
1
- import s from"node:path";export function vitestTestFailureDetails(t,e){if(!Array.isArray(t))throw new Error("Vitest native report failures must be an array");const i=new Set;return t.map(n=>{if(typeof n!="object"||n===null||Array.isArray(n))throw new Error("Vitest native report failure must contain an object");const r=n,u=l(r.id,"id");if(i.has(u))throw new Error("Vitest native report contains a duplicate test identity");i.add(u);const o=l(r.title,"title"),h=w(e,l(r.file,"file")),c=g(r.line),f=g(r.column);if(!Array.isArray(r.messages)||!r.messages.every(a=>typeof a=="string"))throw new Error("Vitest native report failure messages must contain strings");const m=r.messages.map(a=>a.trim()).filter(Boolean),p=m.slice(1).join(`
2
- `);return{classification:"product",count:1,key:o,kind:"vitest-test",location:{path:h,...c===null?{}:{line:c},...f===null?{}:{column:f}},message:m[0]??`Vitest assertion failed: ${o}`,...p.length===0?{}:{stack:p},title:o}})}function w(t,e){if(!s.isAbsolute(e))return e;const i=s.relative(t,e);return i.length===0||i.startsWith("..")||s.isAbsolute(i)?e:i.split(s.sep).join("/")}function g(t){if(t===null)return null;if(typeof t!="number"||!Number.isSafeInteger(t)||t<0)throw new Error("Vitest native report failure coordinate must be a non-negative integer or null");return t}function l(t,e){if(typeof t!="string"||t.trim().length===0)throw new Error(`Vitest native report failure ${e} must be a non-empty string`);return t}
1
+ import s from"node:path";export function vitestTestFailureDetails(t,e){if(!Array.isArray(t))throw new Error("Vitest native report failures must be an array");const r=new Set;return t.map(n=>{if(typeof n!="object"||n===null||Array.isArray(n))throw new Error("Vitest native report failure must contain an object");const i=n,u=l(i.id,"id");if(r.has(u))throw new Error("Vitest native report contains a duplicate test identity");r.add(u);const o=l(i.title,"title"),d=h(e,l(i.file,"file")),c=g(i.line),f=g(i.column);if(!Array.isArray(i.messages)||!i.messages.every(a=>typeof a=="string"))throw new Error("Vitest native report failure messages must contain strings");const m=i.messages.map(a=>a.trim()).filter(Boolean),p=m.slice(1).join(`
2
+ `);return{classification:"product",count:1,key:o,kind:"vitest-test",location:{path:d,...c===null?{}:{line:c},...f===null?{}:{column:f}},message:m[0]??`Vitest assertion failed: ${o}`,...p.length===0?{}:{stack:p},title:o}})}export function vitestErrorDetails(t){const e="Vitest error outside a test";return t.map((r,n)=>({count:1,key:`error ${String(n+1)}`,kind:"vitest-error",message:r.trim()||e,title:e}))}function h(t,e){if(!s.isAbsolute(e))return e;const r=s.relative(t,e);return r.length===0||r.startsWith("..")||s.isAbsolute(r)?e:r.split(s.sep).join("/")}function g(t){if(t===null)return null;if(typeof t!="number"||!Number.isSafeInteger(t)||t<0)throw new Error("Vitest native report failure coordinate must be a non-negative integer or null");return t}function l(t,e){if(typeof t!="string"||t.trim().length===0)throw new Error(`Vitest native report failure ${e} must be a non-empty string`);return t}
@@ -1 +1 @@
1
- import{makeTempDirAsync as D}from"../../../process/files/temp-dir.js";import{classifyManagedProcessResult as F,terminalOutcome as T,processOutputEvidence as N,failureFromError as P,firstOutputLine as R,processExitFailure as C,toError as h,executeEngineTasks as M,managedProcessFailure as x,emptyEvidence as b,taskTimeoutFailure as W}from"../../adapters/execution.js";import{requireRootDirectory as B,requireAbsoluteWorkspacePath as O,requireConsumerFile as Y,safeSegment as S}from"../../adapters/workspace.js";import u from"node:fs/promises";import H from"node:path";import{setTimeout as U}from"node:timers/promises";import{pathToFileURL as $}from"node:url";import{isEngineTaskDeadlineExceededReason as K,resolveEngineTaskInputs as G}from"../../api/index.js";import{createNativeInvocationProcessEnvironment as V,runManagedProcess as j}from"../../../process/index.js";import{PlaywrightConsumerConfigurationError as J,playwrightExactFileFilter as z,writePlaywrightTaskWorkspace as X}from"./config.js";import{collectPlaywrightEvidence as Q}from"./evidence.js";import{engineEvidenceIdentity as Z}from"../../adapters/evidence-identity.js";import{getNodeStartupCacheEnvironment as q}from"../../adapters/node-startup-cache.js";const y={codePrefix:"PLAYWRIGHT",label:"Playwright"};export const PLAYWRIGHT_SPAWN_ENVIRONMENT_MAX_BYTES=24*1024;const k=64;class L extends Error{constructor(t){const r=h(t);super(r.message,{cause:r}),this.name="PlaywrightConsumerPreparationError"}}export const playwrightAdapterDependencies={createInvocationEnvironment:V,removeOwnedDirectory:et,writeTaskWorkspace:X};const tt=new Set(["EBUSY","ENOTEMPTY","EPERM"]),rt=5;async function et(e){for(let t=0;;t+=1)try{await u.rm(e,{force:!0,recursive:!0});return}catch(r){const i=r instanceof Error&&"code"in r?r.code:void 0;if(t===rt||typeof i!="string"||!tt.has(i))throw r;await U((t+1)*100)}}export async function executePlaywrightEngine(e,t,r,i=playwrightAdapterDependencies){const o=await it(e,t,i,r);return M(e,o.tasks,y,(a,s)=>ot(e,o,a,s),ct(e,o.installation))}async function it(e,t,r,i){const o=await B(e.workspace.rootDirectory);O(e.workspace.temporaryDirectory,"temporary directory"),O(e.workspace.artifactDirectory,"artifact directory"),await u.mkdir(e.workspace.temporaryDirectory,{recursive:!0}),await u.mkdir(e.workspace.artifactDirectory,{recursive:!0});const a=await u.realpath(e.workspace.temporaryDirectory),s=await u.realpath(e.workspace.artifactDirectory),n=await D(`playwright-${S(e.invocationId)}-`,{parentDir:a});i.defer(()=>r.removeOwnedDirectory(n));const l=await r.createInvocationEnvironment(n);i.defer(()=>r.removeOwnedDirectory(l.temporaryDirectory));const p=[];for(const[g,c]of e.tasks.entries()){const I=G(e,c),v=`${g.toString().padStart(3,"0")}-${S(c.id)}`,_=H.join(n,v),f=await D(`${v}-`,{parentDir:s});try{const m=st(e,c.serviceName),E=await lt(()=>Y(o,c.file,"Playwright task file")),w=await r.writeTaskWorkspace({artifactDirectory:f,baseUrl:m,browserFile:t.browserExecutable,endpoints:e.target.endpoints,inputs:I,invocationId:e.invocationId,managedModuleEntrypoints:{authoring:t.authoringEntrypoints.authoring,cli:t.family.cli,playwright:t.family.playwright.entrypoint,playwrightCore:t.family.playwrightCore.entrypoint,playwrightTest:t.family.playwrightTest.entrypoint,root:t.authoringEntrypoints.root,rootPackageJson:t.authoringEntrypoints.rootPackageJson},moduleFile:t.family.playwrightTest.entrypoint,orchestration:c.orchestration,rootDirectory:o,serviceName:c.serviceName,sourceFile:E,taskDirectory:_,taskId:c.id});i.defer(async()=>{try{await r.removeOwnedDirectory(w.attachmentDirectory)}catch(d){if(!(d instanceof Error&&"code"in d&&d.code==="ENOTDIR"))throw d}}),p.push({artifactDirectory:f,inputs:I,task:c,preparationFailure:null,prepared:{baseUrl:m,sourceFile:E,workspace:w}})}catch(m){const E=(await pt([_,f],r)).map(w=>w.error);p.push({artifactDirectory:f,inputs:I,task:c,preparationFailure:playwrightPreparationFailure(m,E),prepared:null})}}return Object.freeze({installation:t,invocationEnvironment:l.environment,rootDirectory:o,tasks:Object.freeze(p)})}async function ot(e,t,r,i){const a={...await q(e.workspace.cacheDirectory,r.inputs.environment),...nt(e,t,r)};try{A(a)}catch{delete a.NODE_COMPILE_CACHE}const s=r.task.deadlineMs-Date.now();if(s<=0)return T(r.task.id,"failed",i,b(),W(r.task.timeoutMs,y));const n=await j({args:[t.installation.family.cli,"test",z(r.prepared.sourceFile),"--config",r.prepared.workspace.configFile],cwd:t.rootDirectory,displayLabel:"Playwright",env:a,executable:process.execPath,signal:e.signal,timeoutMs:s});let l;try{l=await Q({identity:{engineId:"playwright",invocationId:e.invocationId,taskId:r.task.id,sourceFile:r.prepared.sourceFile},artifactDirectory:r.artifactDirectory,outputDirectory:r.prepared.workspace.outputDirectory,reportFile:r.prepared.workspace.reportFile,rootDirectory:t.rootDirectory,sourceFile:r.prepared.sourceFile,stderr:n.stderr,stdout:n.stdout})}catch(g){return T(r.task.id,"failed",i,N(n),playwrightEvidenceCollectionFailure(n,g))}const p=classifyPlaywrightProcessResult(n,l,r.task.timeoutMs,K(e.signal.reason));return T(r.task.id,p.status,i,l.evidence,p.failure)}export function classifyPlaywrightProcessResult(e,t,r,i){const o=F(e,r,i,y);if(o!==null)return o;if(t.outputLimit!==null){const n=t.outputLimit;return{failure:{code:"PLAYWRIGHT_OUTPUT_ARTIFACT_LIMIT",details:{...n},message:`Playwright output exceeded its ${n.limit.toString()} ${n.kind==="bytes"?"byte aggregate":"entry"} artifact limit`},status:"failed"}}const a=t.attachmentFailures.find(n=>n.code==="PLAYWRIGHT_ATTACHMENT_LIMIT");if(a!==void 0)return{failure:a,status:"failed"};if(t.report.failure!==null)return t.report;const s=t.attachmentFailures.at(0);return s!==void 0?{failure:s,status:"failed"}:e.exitCode!==0?C(e,y,R(e.stderr)??R(e.stdout)):t.reportError!==null?{failure:{...P(t.reportError,"PLAYWRIGHT_REPORT_INVALID"),classification:"infra"},status:"failed"}:t.report}export function playwrightEvidenceCollectionFailure(e,t){const r=h(t);return x(e,y,r)??{...P(r,"PLAYWRIGHT_EVIDENCE_FAILED"),classification:"infra"}}function nt(e,t,r){const i={...t.invocationEnvironment},o=r.task,a=r.prepared.workspace.inputBootstrapFile,s=r.prepared.workspace.resolutionBootstrapFile;return Object.assign(i,{BASE_URL:r.prepared.baseUrl,FORCE_COLOR:"0",NO_COLOR:"1",PLAYWRIGHT_BROWSERS_PATH:t.installation.registry.canonicalValue,PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD:"1",TESTKIT_ACTIVE:"1",TESTKIT_INTERNAL_FILE_DEADLINE_MS:String(o.deadlineMs),TESTKIT_INTERNAL_FILE_TIMEOUT_SECONDS:String(Math.max(1,Math.ceil(o.timeoutMs/1e3))),TESTKIT_INVOCATION_ID:e.invocationId,TESTKIT_LEASE_DIR:o.orchestration.lease.directory,TESTKIT_LEASE_ID:o.orchestration.lease.id,TESTKIT_MODE:o.orchestration.mode,...o.orchestration.runtimeId===void 0?{}:{TESTKIT_RUNTIME_ID:o.orchestration.runtimeId},TESTKIT_TEST_FILE:o.file,TESTKIT_TEST_ID:o.id}),i.NODE_OPTIONS=at(s,a),A(i),i}function at(e,t){return["--experimental-strip-types",`--require=${JSON.stringify(e)}`,`--import=${JSON.stringify($(t).href)}`].join(" ")}export function playwrightSpawnEnvironmentBytes(e){return Object.entries(e).reduce((t,[r,i])=>t+(process.platform==="win32"?(r.length+i.length+2)*2:Buffer.byteLength(r,"utf8")+Buffer.byteLength(i,"utf8")+2),0)}function A(e){if(Object.keys(e).length>k)throw new Error(`Playwright physical spawn environment exceeds its ${k.toString()} entry limit`);if(playwrightSpawnEnvironmentBytes(e)>PLAYWRIGHT_SPAWN_ENVIRONMENT_MAX_BYTES)throw new Error(`Playwright physical spawn environment exceeds its ${PLAYWRIGHT_SPAWN_ENVIRONMENT_MAX_BYTES.toString()} byte limit`)}function st(e,t){const r=e.target.endpoints[t];if(typeof r!="string"||r.trim().length===0)throw new Error(`Playwright task service ${t} does not have a published endpoint`);let i;try{i=new URL(r)}catch(o){throw new Error(`Playwright task service ${t} endpoint is not a valid URL`,{cause:o})}if(i.protocol!=="http:"&&i.protocol!=="https:")throw new Error(`Playwright task service ${t} endpoint must use http or https`);return i.href}function ct(e,t){return{...Z({engineId:"playwright",installationKind:"root-playwright-family",profile:e.profile}),browserName:"chromium",packages:{playwright:t.family.playwright.version,playwrightCore:t.family.playwrightCore.version,playwrightTest:t.family.playwrightTest.version},registryMode:t.registry.mode}}export function playwrightPreparationFailure(e,t){if(t.length===0)return{...P(e,"PLAYWRIGHT_PREPARE_FAILED"),classification:e instanceof L||e instanceof J?"product":"infra"};const r=h(e);return{classification:"infra",code:"PLAYWRIGHT_PREPARE_CLEANUP_FAILED",details:{cleanupFailures:t.map(i=>i.message),preparationFailure:r.message},message:`Playwright task preparation failed and its partial workspace could not be removed: ${t.map(i=>i.message).join("; ")}`,...r.stack===void 0?{}:{stack:r.stack}}}async function lt(e){try{return await e()}catch(t){throw new L(t)}}async function pt(e,t){const r=[];for(const i of e)try{await t.removeOwnedDirectory(i)}catch(o){r.push({directory:i,error:h(o)})}return r}
1
+ import{makeTempDirAsync as D}from"../../../process/files/temp-dir.js";import{classifyManagedProcessResult as F,terminalOutcome as T,processOutputEvidence as N,failureFromError as P,firstOutputLine as R,processExitDetails as C,processExitFailure as x,toError as h,executeEngineTasks as M,managedProcessFailure as b,emptyEvidence as W,taskTimeoutFailure as B}from"../../adapters/execution.js";import{requireRootDirectory as Y,requireAbsoluteWorkspacePath as O,requireConsumerFile as H,safeSegment as S}from"../../adapters/workspace.js";import u from"node:fs/promises";import U from"node:path";import{setTimeout as $}from"node:timers/promises";import{pathToFileURL as K}from"node:url";import{isEngineTaskDeadlineExceededReason as G,resolveEngineTaskInputs as V}from"../../api/index.js";import{createNativeInvocationProcessEnvironment as j,runManagedProcess as J}from"../../../process/index.js";import{PlaywrightConsumerConfigurationError as z,playwrightExactFileFilter as X,writePlaywrightTaskWorkspace as Q}from"./config.js";import{collectPlaywrightEvidence as Z}from"./evidence.js";import{engineEvidenceIdentity as q}from"../../adapters/evidence-identity.js";import{getNodeStartupCacheEnvironment as rr}from"../../adapters/node-startup-cache.js";const y={codePrefix:"PLAYWRIGHT",label:"Playwright"};export const PLAYWRIGHT_SPAWN_ENVIRONMENT_MAX_BYTES=24*1024;const k=64;class L extends Error{constructor(r){const e=h(r);super(e.message,{cause:e}),this.name="PlaywrightConsumerPreparationError"}}export const playwrightAdapterDependencies={createInvocationEnvironment:j,removeOwnedDirectory:ir,writeTaskWorkspace:Q};const tr=new Set(["EBUSY","ENOTEMPTY","EPERM"]),er=5;async function ir(t){for(let r=0;;r+=1)try{await u.rm(t,{force:!0,recursive:!0});return}catch(e){const i=e instanceof Error&&"code"in e?e.code:void 0;if(r===er||typeof i!="string"||!tr.has(i))throw e;await $((r+1)*100)}}export async function executePlaywrightEngine(t,r,e,i=playwrightAdapterDependencies){const o=await or(t,r,i,e);return M(t,o.tasks,y,(a,s)=>nr(t,o,a,s),lr(t,o.installation))}async function or(t,r,e,i){const o=await Y(t.workspace.rootDirectory);O(t.workspace.temporaryDirectory,"temporary directory"),O(t.workspace.artifactDirectory,"artifact directory"),await u.mkdir(t.workspace.temporaryDirectory,{recursive:!0}),await u.mkdir(t.workspace.artifactDirectory,{recursive:!0});const a=await u.realpath(t.workspace.temporaryDirectory),s=await u.realpath(t.workspace.artifactDirectory),n=await D(`playwright-${S(t.invocationId)}-`,{parentDir:a});i.defer(()=>e.removeOwnedDirectory(n));const l=await e.createInvocationEnvironment(n);i.defer(()=>e.removeOwnedDirectory(l.temporaryDirectory));const p=[];for(const[g,c]of t.tasks.entries()){const I=V(t,c),v=`${g.toString().padStart(3,"0")}-${S(c.id)}`,_=U.join(n,v),f=await D(`${v}-`,{parentDir:s});try{const m=cr(t,c.serviceName),E=await pr(()=>H(o,c.file,"Playwright task file")),w=await e.writeTaskWorkspace({artifactDirectory:f,baseUrl:m,browserFile:r.browserExecutable,endpoints:t.target.endpoints,inputs:I,invocationId:t.invocationId,managedModuleEntrypoints:{authoring:r.authoringEntrypoints.authoring,cli:r.family.cli,playwright:r.family.playwright.entrypoint,playwrightCore:r.family.playwrightCore.entrypoint,playwrightTest:r.family.playwrightTest.entrypoint,root:r.authoringEntrypoints.root,rootPackageJson:r.authoringEntrypoints.rootPackageJson},moduleFile:r.family.playwrightTest.entrypoint,orchestration:c.orchestration,rootDirectory:o,serviceName:c.serviceName,sourceFile:E,taskDirectory:_,taskId:c.id});i.defer(async()=>{try{await e.removeOwnedDirectory(w.attachmentDirectory)}catch(d){if(!(d instanceof Error&&"code"in d&&d.code==="ENOTDIR"))throw d}}),p.push({artifactDirectory:f,inputs:I,task:c,preparationFailure:null,prepared:{baseUrl:m,sourceFile:E,workspace:w}})}catch(m){const E=(await ur([_,f],e)).map(w=>w.error);p.push({artifactDirectory:f,inputs:I,task:c,preparationFailure:playwrightPreparationFailure(m,E),prepared:null})}}return Object.freeze({installation:r,invocationEnvironment:l.environment,rootDirectory:o,tasks:Object.freeze(p)})}async function nr(t,r,e,i){const a={...await rr(t.workspace.cacheDirectory,e.inputs.environment),...ar(t,r,e)};try{A(a)}catch{delete a.NODE_COMPILE_CACHE}const s=e.task.deadlineMs-Date.now();if(s<=0)return T(e.task.id,"failed",i,W(),B(e.task.timeoutMs,y));const n=await J({args:[r.installation.family.cli,"test",X(e.prepared.sourceFile),"--config",e.prepared.workspace.configFile],cwd:r.rootDirectory,displayLabel:"Playwright",env:a,executable:process.execPath,signal:t.signal,timeoutMs:s});let l;try{l=await Z({identity:{engineId:"playwright",invocationId:t.invocationId,taskId:e.task.id,sourceFile:e.prepared.sourceFile},artifactDirectory:e.artifactDirectory,outputDirectory:e.prepared.workspace.outputDirectory,reportFile:e.prepared.workspace.reportFile,rootDirectory:r.rootDirectory,sourceFile:e.prepared.sourceFile,stderr:n.stderr,stdout:n.stdout})}catch(g){return T(e.task.id,"failed",i,N(n),playwrightEvidenceCollectionFailure(n,g))}const p=classifyPlaywrightProcessResult(n,l,e.task.timeoutMs,G(t.signal.reason));return T(e.task.id,p.status,i,l.evidence,p.failure)}export function classifyPlaywrightProcessResult(t,r,e,i){const o=F(t,e,i,y);if(o!==null)return o;if(r.outputLimit!==null){const n=r.outputLimit;return{failure:{code:"PLAYWRIGHT_OUTPUT_ARTIFACT_LIMIT",details:{...n},message:`Playwright output exceeded its ${n.limit.toString()} ${n.kind==="bytes"?"byte aggregate":"entry"} artifact limit`},status:"failed"}}const a=r.attachmentFailures.find(n=>n.code==="PLAYWRIGHT_ATTACHMENT_LIMIT");if(a!==void 0)return{failure:a,status:"failed"};if(r.report.failure!==null){const n=r.report.failure;return{...r.report,failure:{...n,details:{...n.details,evidenceErrors:r.reportError===null?0:1,...C(t)}}}}const s=r.attachmentFailures.at(0);return s!==void 0?{failure:s,status:"failed"}:t.exitCode!==0?x(t,y,R(t.stderr)??R(t.stdout)):r.reportError!==null?{failure:{...P(r.reportError,"PLAYWRIGHT_REPORT_INVALID"),classification:"infra"},status:"failed"}:r.report}export function playwrightEvidenceCollectionFailure(t,r){const e=h(r);return b(t,y,e)??{...P(e,"PLAYWRIGHT_EVIDENCE_FAILED"),classification:"infra"}}function ar(t,r,e){const i={...r.invocationEnvironment},o=e.task,a=e.prepared.workspace.inputBootstrapFile,s=e.prepared.workspace.resolutionBootstrapFile;return Object.assign(i,{BASE_URL:e.prepared.baseUrl,FORCE_COLOR:"0",NO_COLOR:"1",PLAYWRIGHT_BROWSERS_PATH:r.installation.registry.canonicalValue,PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD:"1",TESTKIT_ACTIVE:"1",TESTKIT_INTERNAL_FILE_DEADLINE_MS:String(o.deadlineMs),TESTKIT_INTERNAL_FILE_TIMEOUT_SECONDS:String(Math.max(1,Math.ceil(o.timeoutMs/1e3))),TESTKIT_INVOCATION_ID:t.invocationId,TESTKIT_LEASE_DIR:o.orchestration.lease.directory,TESTKIT_LEASE_ID:o.orchestration.lease.id,TESTKIT_MODE:o.orchestration.mode,...o.orchestration.runtimeId===void 0?{}:{TESTKIT_RUNTIME_ID:o.orchestration.runtimeId},TESTKIT_TEST_FILE:o.file,TESTKIT_TEST_ID:o.id}),i.NODE_OPTIONS=sr(s,a),A(i),i}function sr(t,r){return["--experimental-strip-types",`--require=${JSON.stringify(t)}`,`--import=${JSON.stringify(K(r).href)}`].join(" ")}export function playwrightSpawnEnvironmentBytes(t){return Object.entries(t).reduce((r,[e,i])=>r+(process.platform==="win32"?(e.length+i.length+2)*2:Buffer.byteLength(e,"utf8")+Buffer.byteLength(i,"utf8")+2),0)}function A(t){if(Object.keys(t).length>k)throw new Error(`Playwright physical spawn environment exceeds its ${k.toString()} entry limit`);if(playwrightSpawnEnvironmentBytes(t)>PLAYWRIGHT_SPAWN_ENVIRONMENT_MAX_BYTES)throw new Error(`Playwright physical spawn environment exceeds its ${PLAYWRIGHT_SPAWN_ENVIRONMENT_MAX_BYTES.toString()} byte limit`)}function cr(t,r){const e=t.target.endpoints[r];if(typeof e!="string"||e.trim().length===0)throw new Error(`Playwright task service ${r} does not have a published endpoint`);let i;try{i=new URL(e)}catch(o){throw new Error(`Playwright task service ${r} endpoint is not a valid URL`,{cause:o})}if(i.protocol!=="http:"&&i.protocol!=="https:")throw new Error(`Playwright task service ${r} endpoint must use http or https`);return i.href}function lr(t,r){return{...q({engineId:"playwright",installationKind:"root-playwright-family",profile:t.profile}),browserName:"chromium",packages:{playwright:r.family.playwright.version,playwrightCore:r.family.playwrightCore.version,playwrightTest:r.family.playwrightTest.version},registryMode:r.registry.mode}}export function playwrightPreparationFailure(t,r){if(r.length===0)return{...P(t,"PLAYWRIGHT_PREPARE_FAILED"),classification:t instanceof L||t instanceof z?"product":"infra"};const e=h(t);return{classification:"infra",code:"PLAYWRIGHT_PREPARE_CLEANUP_FAILED",details:{cleanupFailures:r.map(i=>i.message),preparationFailure:e.message},message:`Playwright task preparation failed and its partial workspace could not be removed: ${r.map(i=>i.message).join("; ")}`,...e.stack===void 0?{}:{stack:e.stack}}}async function pr(t){try{return await t()}catch(r){throw new L(r)}}async function ur(t,r){const e=[];for(const i of t)try{await r.removeOwnedDirectory(i)}catch(o){e.push({directory:i,error:h(o)})}return e}
@@ -3,6 +3,7 @@ export declare const MAX_ATTACHMENT_BYTES: number;
3
3
  export declare const MAX_TOTAL_ATTACHMENT_BYTES: number;
4
4
  export interface PlaywrightReporterOptions extends NativeReportOptions {
5
5
  readonly attachmentDirectory: string;
6
+ readonly failOnFlakyTests: boolean;
6
7
  }
7
8
  export type PlaywrightAttachmentError = {
8
9
  readonly kind: "individual" | "total";
@@ -1,4 +1,4 @@
1
- import{ownedRuntimeInputModuleUrl as w}from"../../runtime-input-file.js";import b from"node:crypto";import n from"node:fs/promises";import o from"node:path";import{fileURLToPath as j,pathToFileURL as g}from"node:url";import{managedNodeModuleResolutionBootstrapSource as P}from"../../managed-module-resolution.js";import{materializePlaywrightRuntimeInput as v,playwrightRuntimeInputBootstrapSource as T}from"../runtime/runtime-input.js";import{nativeReporterFile as k}from"../../adapters/native-report.js";export class PlaywrightConsumerConfigurationError extends Error{constructor(r){const t=r instanceof Error?r:new Error(String(r));super(t.message,{cause:t}),this.name="PlaywrightConsumerConfigurationError"}}export function playwrightExactFileFilter(e){return d(e)}export async function writePlaywrightTaskWorkspace(e){let r;try{F(e.inputs.supportModules),r=await D(e.rootDirectory,e.inputs)}catch(m){throw new PlaywrightConsumerConfigurationError(m)}await n.mkdir(e.taskDirectory,{recursive:!0}),await n.mkdir(e.artifactDirectory,{recursive:!0});const t=b.randomUUID(),i=o.join(e.taskDirectory,`playwright-${t}.config.mjs`),a=o.join(e.taskDirectory,`playwright-${t}.input-bootstrap.mjs`),l=o.join(e.taskDirectory,`playwright-${t}.resolution-bootstrap.cjs`),f=o.join(e.taskDirectory,"testkit-playwright-report.json"),p=o.join(e.artifactDirectory,"playwright-output");await n.mkdir(p);const c=o.join(e.artifactDirectory,"playwright-attachments");await n.mkdir(c,{mode:448,recursive:!1});const u=await v({baseUrl:e.baseUrl,directory:e.taskDirectory,endpoints:e.endpoints,inputs:e.inputs,invocationId:e.invocationId,orchestration:e.orchestration,taskId:e.taskId}),y=k("playwright");return await h(l,playwrightResolutionBootstrapSource(e.managedModuleEntrypoints,{consumerRoots:[e.rootDirectory],trustedImporterFiles:[i,a],trustedInternalEntrypoints:[e.moduleFile,y,j(w)]})),await h(a,T({digest:u.digest,file:u.file,sizeBytes:u.sizeBytes,invocationId:e.invocationId,taskId:e.taskId})),await h(i,x({baseConfigFile:r,baseUrl:e.baseUrl,browserFile:e.browserFile,inputBootstrapFile:a,outputDirectory:p,reporterFile:y,report:{attachmentDirectory:c,identity:{engineId:"playwright",invocationId:e.invocationId,taskId:e.taskId,sourceFile:e.sourceFile},reportFile:f,workspaceDirectory:e.taskDirectory},rootDirectory:e.rootDirectory,sourceFile:e.sourceFile})),{attachmentDirectory:c,configFile:i,inputBootstrapFile:a,outputDirectory:p,reportFile:f,resolutionBootstrapFile:l}}export function playwrightResolutionBootstrapSource(e,r){const t={authoring:s(e.authoring,"authoring"),cli:s(e.cli,"cli"),playwright:s(e.playwright,"playwright"),playwrightCore:s(e.playwrightCore,"playwrightCore"),playwrightTest:s(e.playwrightTest,"playwrightTest"),root:s(e.root,"root"),rootPackageJson:s(e.rootPackageJson,"rootPackageJson")},i={"@playwright/test":t.playwrightTest,playwright:t.playwright,"playwright-core":t.playwrightCore};return P({consumerRoots:r.consumerRoots,direct:{"@gfdelarue/testkit":t.root,"@gfdelarue/testkit/package.json":t.rootPackageJson,"@gfdelarue/testkit/playwright":t.authoring},directBridgeRoots:[o.dirname(t.root)],engineLabel:"Playwright",forbiddenConsumerPackagePrefixes:[],packageAnchors:i,reservedPackageNames:["@gfdelarue/testkit"],stateSymbol:"@gfdelarue/testkit/playwright-resolution-hooks/v1",trustedImporterFiles:r.trustedImporterFiles,trustedInternalEntrypoints:r.trustedInternalEntrypoints,verifiedParentlessPackageFiles:{playwright:["lib/common/process.js"]},verifiedProcessEntrypoints:[t.cli]})}function s(e,r){if(!o.isAbsolute(e))throw new Error(`Resolved Playwright installation ${r} entrypoint must be absolute`);return e}function x(e){return`
1
+ import{ownedRuntimeInputModuleUrl as w}from"../../runtime-input-file.js";import b from"node:crypto";import n from"node:fs/promises";import o from"node:path";import{fileURLToPath as j,pathToFileURL as g}from"node:url";import{managedNodeModuleResolutionBootstrapSource as P}from"../../managed-module-resolution.js";import{materializePlaywrightRuntimeInput as v,playwrightRuntimeInputBootstrapSource as T}from"../runtime/runtime-input.js";import{nativeReporterFile as k}from"../../adapters/native-report.js";export class PlaywrightConsumerConfigurationError extends Error{constructor(r){const t=r instanceof Error?r:new Error(String(r));super(t.message,{cause:t}),this.name="PlaywrightConsumerConfigurationError"}}export function playwrightExactFileFilter(e){return d(e)}export async function writePlaywrightTaskWorkspace(e){let r;try{x(e.inputs.supportModules),r=await O(e.rootDirectory,e.inputs)}catch(m){throw new PlaywrightConsumerConfigurationError(m)}await n.mkdir(e.taskDirectory,{recursive:!0}),await n.mkdir(e.artifactDirectory,{recursive:!0});const t=b.randomUUID(),i=o.join(e.taskDirectory,`playwright-${t}.config.mjs`),a=o.join(e.taskDirectory,`playwright-${t}.input-bootstrap.mjs`),l=o.join(e.taskDirectory,`playwright-${t}.resolution-bootstrap.cjs`),f=o.join(e.taskDirectory,"testkit-playwright-report.json"),p=o.join(e.artifactDirectory,"playwright-output");await n.mkdir(p);const c=o.join(e.artifactDirectory,"playwright-attachments");await n.mkdir(c,{mode:448,recursive:!1});const u=await v({baseUrl:e.baseUrl,directory:e.taskDirectory,endpoints:e.endpoints,inputs:e.inputs,invocationId:e.invocationId,orchestration:e.orchestration,taskId:e.taskId}),y=k("playwright");return await h(l,playwrightResolutionBootstrapSource(e.managedModuleEntrypoints,{consumerRoots:[e.rootDirectory],trustedImporterFiles:[i,a],trustedInternalEntrypoints:[e.moduleFile,y,j(w)]})),await h(a,T({digest:u.digest,file:u.file,sizeBytes:u.sizeBytes,invocationId:e.invocationId,taskId:e.taskId})),await h(i,F({baseConfigFile:r,baseUrl:e.baseUrl,browserFile:e.browserFile,inputBootstrapFile:a,outputDirectory:p,reporterFile:y,report:{attachmentDirectory:c,identity:{engineId:"playwright",invocationId:e.invocationId,taskId:e.taskId,sourceFile:e.sourceFile},reportFile:f,workspaceDirectory:e.taskDirectory},rootDirectory:e.rootDirectory,sourceFile:e.sourceFile})),{attachmentDirectory:c,configFile:i,inputBootstrapFile:a,outputDirectory:p,reportFile:f,resolutionBootstrapFile:l}}export function playwrightResolutionBootstrapSource(e,r){const t={authoring:s(e.authoring,"authoring"),cli:s(e.cli,"cli"),playwright:s(e.playwright,"playwright"),playwrightCore:s(e.playwrightCore,"playwrightCore"),playwrightTest:s(e.playwrightTest,"playwrightTest"),root:s(e.root,"root"),rootPackageJson:s(e.rootPackageJson,"rootPackageJson")},i={"@playwright/test":t.playwrightTest,playwright:t.playwright,"playwright-core":t.playwrightCore};return P({consumerRoots:r.consumerRoots,direct:{"@gfdelarue/testkit":t.root,"@gfdelarue/testkit/package.json":t.rootPackageJson,"@gfdelarue/testkit/playwright":t.authoring},directBridgeRoots:[o.dirname(t.root)],engineLabel:"Playwright",forbiddenConsumerPackagePrefixes:[],packageAnchors:i,reservedPackageNames:["@gfdelarue/testkit"],stateSymbol:"@gfdelarue/testkit/playwright-resolution-hooks/v1",trustedImporterFiles:r.trustedImporterFiles,trustedInternalEntrypoints:r.trustedInternalEntrypoints,verifiedParentlessPackageFiles:{playwright:["lib/common/process.js"]},verifiedProcessEntrypoints:[t.cli]})}function s(e,r){if(!o.isAbsolute(e))throw new Error(`Resolved Playwright installation ${r} entrypoint must be absolute`);return e}function F(e){return`
2
2
  import path from "node:path";
3
3
  import { createRequire } from "node:module";
4
4
 
@@ -92,7 +92,7 @@ function normalizeReporters(value) {
92
92
  : value;
93
93
  return [
94
94
  ...entries.map(normalizeReporterEntry).filter(Boolean),
95
- [OPTIONS.reporterFile, OPTIONS.report],
95
+ [OPTIONS.reporterFile, { ...OPTIONS.report, failOnFlakyTests: Boolean(base.failOnFlakyTests) }],
96
96
  ];
97
97
  }
98
98
 
@@ -164,4 +164,4 @@ export default {
164
164
  fullyParallel: false,
165
165
  workers: 1,
166
166
  };
167
- `}function d(e){return`^${e.split(/[\\/]+/u).map(r=>r.replace(/[.*+?^${}()|[\]\\]/gu,"\\$&")).join("[\\\\/]")}$`}async function D(e,r){if(!Object.hasOwn(r.supportModules,"playwrightConfig"))return null;const t=r.supportModules.playwrightConfig;if(!o.isAbsolute(t))throw new Error("supportModules.playwrightConfig must be an absolute path");return I(e,t,"supportModules.playwrightConfig")}function F(e){const r=Object.keys(e).filter(t=>t!=="playwrightConfig");if(r.length>0)throw new Error(`Playwright engine does not support support module(s): ${r.join(", ")}`)}async function I(e,r,t){const i=o.resolve(e,r),a=await n.realpath(i);if(!O(e,a))throw new Error(`${t} must stay within the consumer root`);if(!(await n.stat(a)).isFile())throw new Error(`${t} must be a regular file`);return a}function O(e,r){const t=o.relative(e,r);return t===""||!t.startsWith("..")&&!o.isAbsolute(t)}async function h(e,r){const t=await n.open(e,"wx",384);try{await t.writeFile(r,"utf8")}finally{await t.close()}}
167
+ `}function d(e){return`^${e.split(/[\\/]+/u).map(r=>r.replace(/[.*+?^${}()|[\]\\]/gu,"\\$&")).join("[\\\\/]")}$`}async function O(e,r){if(!Object.hasOwn(r.supportModules,"playwrightConfig"))return null;const t=r.supportModules.playwrightConfig;if(!o.isAbsolute(t))throw new Error("supportModules.playwrightConfig must be an absolute path");return D(e,t,"supportModules.playwrightConfig")}function x(e){const r=Object.keys(e).filter(t=>t!=="playwrightConfig");if(r.length>0)throw new Error(`Playwright engine does not support support module(s): ${r.join(", ")}`)}async function D(e,r,t){const i=o.resolve(e,r),a=await n.realpath(i);if(!I(e,a))throw new Error(`${t} must stay within the consumer root`);if(!(await n.stat(a)).isFile())throw new Error(`${t} must be a regular file`);return a}function I(e,r){const t=o.relative(e,r);return t===""||!t.startsWith("..")&&!o.isAbsolute(t)}async function h(e,r){const t=await n.open(e,"wx",384);try{await t.writeFile(r,"utf8")}finally{await t.close()}}