@gfdelarue/testkit 0.6.3 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +297 -3
- package/dist/app/bugs/batch.js +1 -1
- package/dist/app/bugs/bisect-dependencies.js +1 -0
- package/dist/app/bugs/bisect-identity.js +1 -0
- package/dist/app/bugs/bisect-report.js +1 -0
- package/dist/app/bugs/bisect-search.js +1 -0
- package/dist/app/bugs/bisect.js +2 -0
- package/dist/app/bugs/draft.js +3 -3
- package/dist/app/bugs/facts.js +2 -2
- package/dist/app/bugs/file.js +1 -1
- package/dist/app/bugs/issue.js +4 -4
- package/dist/app/bugs/record.js +2 -2
- package/dist/app/campaigns/brief.js +5 -5
- package/dist/app/prove/changes.js +3 -0
- package/dist/app/prove/index.js +2 -0
- package/dist/app/prove/mutants.js +3 -0
- package/dist/app/prove/report.js +3 -0
- package/dist/app/prove/verdict.js +1 -0
- package/dist/app/run/engine-orchestration.js +1 -1
- package/dist/app/run/orchestrator.js +1 -1
- package/dist/app/run/request.js +1 -1
- package/dist/app/run/time-zone.js +1 -0
- package/dist/app/sandbox/index.js +1 -0
- package/dist/app/sandbox/run.js +1 -0
- package/dist/app/shake/conditions.js +1 -0
- package/dist/app/shake/index.js +4 -0
- package/dist/app/shake/report.js +6 -0
- package/dist/app/shake/verdicts.js +1 -0
- package/dist/cli/command-catalog.js +1 -1
- package/dist/cli/command-flags.js +1 -1
- package/dist/cli/command-registry.js +1 -1
- package/dist/cli/commands/bug/bisect.js +1 -0
- package/dist/cli/commands/bug/file.js +3 -3
- package/dist/cli/commands/bug/record.js +2 -2
- package/dist/cli/commands/prove.js +3 -0
- package/dist/cli/commands/run.js +2 -2
- package/dist/cli/commands/shake.js +1 -0
- package/dist/domain/file-timeout.js +1 -1
- package/dist/engines/adapters/execution.js +1 -1
- package/dist/engines/contracts/fixture-manifest.js +1 -1
- package/dist/engines/node-vitest/adapter/adapter.js +1 -1
- package/dist/engines/node-vitest/adapter/driver.js +2 -2
- package/dist/engines/node-vitest/adapter/reporter.js +1 -1
- package/dist/engines/node-vitest/selection.js +1 -1
- package/dist/engines/playwright/adapter/adapter.js +1 -1
- package/dist/engines/playwright/adapter/config.js +28 -5
- package/dist/engines/playwright/adapter/evidence.js +2 -2
- package/dist/engines/playwright/runtime/index.js +1 -1
- package/dist/engines/playwright/runtime/sandbox.js +1 -1
- package/dist/process/environment.js +1 -1
- package/dist/process/execa-bundle.json +1 -1
- package/dist/repo/history.js +5 -5
- package/dist/toolchains/host-node-support.js +1 -1
- package/dist/types/playwright.d.ts +2 -7
- package/dist/types/workspace.d.ts +2 -0
- package/docs/bug-records.md +32 -23
- package/docs/engines.md +1 -1
- package/docs/release-notes.md +95 -0
- package/package.json +22 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
import M from"node:fs/promises";import c from"node:path";import{writeNativeReport as y}from"../../adapters/native-report.js";import{boundEnvironmentValueMissingText as S,environmentValueMissingFailure as F,readEnvironmentValueMissing as f}from"../../contracts/environment-values.js";export const ATTEMPT_ERROR_STARTS_META_KEY="testkitAttemptErrorStarts";export function nodeVitestReportOptions(t,{reportFile:n,sourceFile:s,taskId:i}){return{identity:{engineId:"node-vitest",invocationId:t,sourceFile:s,taskId:i},reportFile:n,workspaceDirectory:c.dirname(n)}}export class TestkitVitestReporter{invocationId;modules;finished=new Map;written=new Map;constructor(n,s){this.invocationId=n,this.modules=s}async onTestModuleEnd(n){const s=this.modules.find(({file:e,project:r})=>c.resolve(e)===c.resolve(n.moduleId)&&r===n.project.name);if(s===void 0)return;const i=[...this.finished.get(s.taskId)??[],n];this.finished.set(s.taskId,i);const a=this.modules.filter(({taskId:e})=>e===s.taskId).length-i.length,l=i.flatMap(e=>[e,...e.children.allSuites()]),o=i.flatMap(e=>[...e.children.allTests()]),u=o.filter(e=>e.result().state==="failed"),d=l.filter(e=>e.state()==="failed").length,g=l.filter(e=>e.state()==="pending"||e.state()==="queued"||e.type==="suite"&&e.options.mode==="todo").length,h=l.flatMap(e=>e.errors().filter(r=>!A(e,r))),T=h.map(reportedError),m=[...o.flatMap(e=>e.result().errors??[]),...h].flatMap(e=>{const r=f(e);return r===null?[]:[{message:e.message,missing:r}]}),k={...a===0?{}:{pending:a},summary:{numFailedTestSuites:d,numFailedTests:u.length,numPassedTestSuites:l.length-d-g,numPassedTests:o.filter(e=>e.result().state==="passed").length,numPendingTestSuites:g,numPendingTests:o.filter(e=>e.options.mode!=="todo"&&(e.result().state==="pending"||e.result().state==="skipped")).length,numTodoTests:o.filter(e=>e.options.mode==="todo").length,numTotalTestSuites:l.length,numTotalTests:o.length,success:d===0&&u.length===0&&T.length===0},...m.length===0?{}:{environmentFailure:F(m.map(({missing:e})=>e),m[0].message)},cases:o.map(e=>({attempts:R(e),file:e.module.moduleId,id:e.id,project:e.project.name,repeat:e.diagnostic()?.repeatCount??0,state:e.result().state,title:I(e)})),failures:u.map(e=>({...e.result().errors?.some(r=>f(r)!==null)?{classification:"infra"}:{},id:e.id,project:e.project.name,title:I(e),file:e.module.moduleId,line:e.location?.line??null,column:e.location?.column??null,errors:v(e).final.map(reportedError),previousAttemptErrors:v(e).earlier.map(reportedError)})),errors:T},p={...s,reportFile:`${s.reportFile}.next`},E=(this.written.get(s.taskId)??Promise.resolve()).then(async()=>{await M.rm(p.reportFile,{force:!0}),await y(nodeVitestReportOptions(this.invocationId,p),k,16*1024*1024),await M.rename(p.reportFile,s.reportFile)});this.written.set(s.taskId,E),await E}}function A(t,n){return t.type==="module"&&n.message===`No test suite found in file ${t.moduleId}`}function v(t){const n=t.result().errors??[],s=t.meta()[ATTEMPT_ERROR_STARTS_META_KEY],i=Array.isArray(s)?s.at(-1):void 0,a=typeof i=="number"&&i<n.length?i:0;return{earlier:n.slice(0,a),final:n.slice(a)}}function R(t){const n=t.diagnostic();return n===void 0?0:n.retryCount+1}export function reportedError(t){return{message:w(t,t.name?`${t.name}: ${t.message}`:t.message),...t.stack?{stack:w(t,t.stack)}:{}}}function w(t,n){return f(t)===null?n:S(n)}function I(t){const n=[];let s=t.parent;for(;s.type==="suite";)n.unshift(s.name),s=s.parent;return[...n,t.name].join(" ")}
|
|
1
|
+
import M from"node:fs/promises";import c from"node:path";import{writeNativeReport as y}from"../../adapters/native-report.js";import{boundEnvironmentValueMissingText as S,environmentValueMissingFailure as F,readEnvironmentValueMissing as f}from"../../contracts/environment-values.js";export const ATTEMPT_ERROR_STARTS_META_KEY="testkitAttemptErrorStarts";export function nodeVitestReportOptions(t,{reportFile:n,sourceFile:s,taskId:i}){return{identity:{engineId:"node-vitest",invocationId:t,sourceFile:s,taskId:i},reportFile:n,workspaceDirectory:c.dirname(n)}}export class TestkitVitestReporter{invocationId;modules;finished=new Map;written=new Map;constructor(n,s){this.invocationId=n,this.modules=s}async onTestModuleEnd(n){const s=this.modules.find(({file:e,project:r})=>c.resolve(e)===c.resolve(n.moduleId)&&r===n.project.name);if(s===void 0)return;const i=[...this.finished.get(s.taskId)??[],n];this.finished.set(s.taskId,i);const a=this.modules.filter(({taskId:e})=>e===s.taskId).length-i.length,l=i.flatMap(e=>[e,...e.children.allSuites()]),o=i.flatMap(e=>[...e.children.allTests()]),u=o.filter(e=>e.result().state==="failed"),d=l.filter(e=>e.state()==="failed").length,g=l.filter(e=>e.state()==="pending"||e.state()==="queued"||e.type==="suite"&&e.options.mode==="todo").length,h=l.flatMap(e=>e.errors().filter(r=>!A(e,r))),T=h.map(reportedError),m=[...o.flatMap(e=>e.result().errors??[]),...h].flatMap(e=>{const r=f(e);return r===null?[]:[{message:e.message,missing:r}]}),k={...a===0?{}:{pending:a},summary:{numFailedTestSuites:d,numFailedTests:u.length,numPassedTestSuites:l.length-d-g,numPassedTests:o.filter(e=>e.result().state==="passed").length,numPendingTestSuites:g,numPendingTests:o.filter(e=>e.options.mode!=="todo"&&(e.result().state==="pending"||e.result().state==="skipped")).length,numTodoTests:o.filter(e=>e.options.mode==="todo").length,numTotalTestSuites:l.length,numTotalTests:o.length,success:d===0&&u.length===0&&T.length===0},...m.length===0?{}:{environmentFailure:F(m.map(({missing:e})=>e),m[0].message)},cases:o.map(e=>({attempts:R(e),file:e.module.moduleId,id:e.id,project:e.project.name,repeat:e.diagnostic()?.repeatCount??0,...e.options.fails===!0?{fails:!0}:{},state:e.result().state,title:I(e)})),failures:u.map(e=>({...e.result().errors?.some(r=>f(r)!==null)?{classification:"infra"}:{},id:e.id,project:e.project.name,title:I(e),file:e.module.moduleId,line:e.location?.line??null,column:e.location?.column??null,errors:v(e).final.map(reportedError),previousAttemptErrors:v(e).earlier.map(reportedError)})),errors:T},p={...s,reportFile:`${s.reportFile}.next`},E=(this.written.get(s.taskId)??Promise.resolve()).then(async()=>{await M.rm(p.reportFile,{force:!0}),await y(nodeVitestReportOptions(this.invocationId,p),k,16*1024*1024),await M.rename(p.reportFile,s.reportFile)});this.written.set(s.taskId,E),await E}}function A(t,n){return t.type==="module"&&n.message===`No test suite found in file ${t.moduleId}`}function v(t){const n=t.result().errors??[],s=t.meta()[ATTEMPT_ERROR_STARTS_META_KEY],i=Array.isArray(s)?s.at(-1):void 0,a=typeof i=="number"&&i<n.length?i:0;return{earlier:n.slice(0,a),final:n.slice(a)}}function R(t){const n=t.diagnostic();return n===void 0?0:n.retryCount+1}export function reportedError(t){return{message:w(t,t.name?`${t.name}: ${t.message}`:t.message),...t.stack?{stack:w(t,t.stack)}:{}}}function w(t,n){return f(t)===null?n:S(n)}function I(t){const n=[];let s=t.parent;for(;s.type==="suite";)n.unshift(s.name),s=s.parent;return[...n,t.name].join(" ")}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import i from"node:fs";import{createRequire as h}from"node:module";import f from"node:path";import{isUnknownRecord as d}from"../../kernel/unknown-values.js";import{isPathWithin as S}from"../../process/files/paths.js";import{NODE_VITEST_ENGINE_ENVIRONMENT_KEYS as v}from"./runtime/runtime-input.js";const N=["auto","managed","repo"];export const NODE_VITEST_INVENTORY_MARKER="TESTKIT_VITEST_INVENTORY=";const g=new Set(["BASE_URL","DATABASE_URL","NODE_OPTIONS",...v].map(e=>e.toUpperCase()));export function resolveVitestSelection(e,t,n=process.env){const{"vitest-config":r,"vitest-project":s,"vitest-runner":a,"vitest-shuffle":o}=e;if(o!==void 0&&!/^\d{1,9}$/u.test(o))throw new Error(`--vitest-shuffle takes a seed of up to 9 digits, not ${JSON.stringify(o)}`);if(a==="managed"&&(r??s)!==void 0)throw new Error("--vitest-config and --vitest-project select the repository's Vitest, not --vitest-runner managed");return{config:r===void 0?null:O(t,r),host:{...Object.fromEntries(Object.entries(n).filter(c=>c[1]!==void 0&&!g.has(c[0].toUpperCase()))),...n.NODE_OPTIONS===void 0?{}:{NODE_OPTIONS:n.NODE_OPTIONS}},project:s??null,runner:a??((r??s)===void 0?"auto":"repo"),shuffle:o===void 0?null:Number(o)}}export function decodeVitestSelection(e){if(!d(e)||!N.includes(e.runner)||![e.config,e.project].every(t=>t===null||typeof t=="string")||!(e.shuffle===null||Number.isInteger(e.shuffle))||!d(e.host)||!Object.values(e.host).every(t=>typeof t=="string"))throw new Error("Node/Vitest selection must name a runner, config, project and host environment");return e}function O(e,t){let n;try{n=i.realpathSync(f.resolve(e,t))}catch(r){throw new Error(`--vitest-config ${t} does not exist in ${e}`,{cause:r})}if(!S(i.realpathSync(e),n))throw new Error(`--vitest-config ${t} resolves outside ${e}`);if(!i.statSync(n).isFile())throw new Error(`--vitest-config ${t} is not a file`);return n}export function resolveRepositoryVitest(e,t=!1){const n=f.join(e,"package.json"),r=h(n);let s;try{s=r.resolve("vitest/package.json")}catch(l){const u=!r.resolve.paths("vitest")?.some(E=>i.lstatSync(f.join(E,"vitest"),{throwIfNoEntry:!1}));if(u&&t&&!w(n))return null;const m=u?"is not installed":"is installed but its package metadata is broken";throw new Error(`Vitest ${m} in ${e}; install vitest 4.x, or pass --vitest-runner managed`,{cause:l})}const a=f.dirname(s),o=JSON.parse(i.readFileSync(s,"utf8")),c=`Vitest ${o.version} at ${a}`;if(!/^4\.\d+\.\d+/u.test(o.version))throw new Error(`${c} is not supported; install vitest 4.x, or pass --vitest-runner managed`);const p=o.exports?.["."]?.import?.default;try{if(typeof p!="string")throw new Error("its package exports name no ESM entry point");return{entrypoint:r.resolve("vitest/node"),index:f.join(a,p),name:"vitest",root:a,version:o.version}}catch(l){throw new Error(`${c} is broken; reinstall it`,{cause:l})}}function w(e){if(!i.existsSync(e))return!1;const t=JSON.parse(i.readFileSync(e,"utf8"));return["dependencies","devDependencies","optionalDependencies"].some(n=>Object.hasOwn(t[n]??{},"vitest"))}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{makeTempDirAsync as D}from"../../../process/files/temp-dir.js";import{toError as g}from"../../../kernel/error-serialization.js";import{classifyManagedProcessResult as A,terminalOutcome as T,processOutputEvidence as N,failureFromError as P,firstOutputLine as R,processExitDetails as M,processExitFailure as b,executeEngineTasks as C,managedProcessFailure as x,emptyEvidence as W,taskTimeoutFailure as U}from"../../adapters/execution.js";import{requireRootDirectory as B,requireAbsoluteWorkspacePath as O,requireConsumerFile as H,safeSegment as S}from"../../adapters/workspace.js";import u from"node:fs/promises";import Y from"node:path";import{setTimeout as $}from"node:timers/promises";import{pathToFileURL as G}from"node:url";import{isEngineTaskDeadlineExceededReason as K,resolveEngineTaskInputs as V}from"../../api/index.js";import{createNativeInvocationProcessEnvironment as j,runManagedProcess as J}from"../../../process/index.js";import{PlaywrightConsumerConfigurationError as z,playwrightFileSuiteGrep 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";import{CHROMIUM_STARTUP_ENVIRONMENT as tr}from"../runtime/chromium-environment.js";const y={codePrefix:"PLAYWRIGHT",label:"Playwright"};export const PLAYWRIGHT_SPAWN_ENVIRONMENT_MAX_BYTES=24*1024;const F=64;class L extends Error{constructor(r){const t=g(r);super(t.message,{cause:t}),this.name="PlaywrightConsumerPreparationError"}}export const playwrightAdapterDependencies={createInvocationEnvironment:j,removeOwnedDirectory:or,writeTaskWorkspace:Q};const er=new Set(["EBUSY","ENOTEMPTY","EPERM"]),ir=5;async function or(e){for(let r=0;;r+=1)try{await u.rm(e,{force:!0,recursive:!0});return}catch(t){const i=t instanceof Error&&"code"in t?t.code:void 0;if(r===ir||typeof i!="string"||!er.has(i))throw t;await $((r+1)*100)}}export async function executePlaywrightEngine(e,r,t,i=playwrightAdapterDependencies){const o=await nr(e,r,i,t);return C(e,o.tasks,y,(a,s)=>ar(e,o,a,s),pr(e,o.installation))}async function nr(e,r,t,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(()=>t.removeOwnedDirectory(n));const l=await t.createInvocationEnvironment(n);i.defer(()=>t.removeOwnedDirectory(l.temporaryDirectory));const p=[];for(const[h,c]of e.tasks.entries()){const I=V(e,c),v=`${h.toString().padStart(3,"0")}-${S(c.id)}`,_=Y.join(n,v),f=await D(`${v}-`,{parentDir:s});try{const m=lr(e,c.serviceName),E=await ur(()=>H(o,c.file,"Playwright task file")),
|
|
1
|
+
import{makeTempDirAsync as D}from"../../../process/files/temp-dir.js";import{toError as g}from"../../../kernel/error-serialization.js";import{classifyManagedProcessResult as A,terminalOutcome as T,processOutputEvidence as N,failureFromError as P,firstOutputLine as R,processExitDetails as M,processExitFailure as b,executeEngineTasks as C,managedProcessFailure as x,emptyEvidence as W,taskTimeoutFailure as U}from"../../adapters/execution.js";import{requireRootDirectory as B,requireAbsoluteWorkspacePath as O,requireConsumerFile as H,safeSegment as S}from"../../adapters/workspace.js";import u from"node:fs/promises";import Y from"node:path";import{setTimeout as $}from"node:timers/promises";import{pathToFileURL as G}from"node:url";import{isEngineTaskDeadlineExceededReason as K,resolveEngineTaskInputs as V}from"../../api/index.js";import{createNativeInvocationProcessEnvironment as j,runManagedProcess as J}from"../../../process/index.js";import{PlaywrightConsumerConfigurationError as z,playwrightFileSuiteGrep 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";import{CHROMIUM_STARTUP_ENVIRONMENT as tr}from"../runtime/chromium-environment.js";const y={codePrefix:"PLAYWRIGHT",label:"Playwright"};export const PLAYWRIGHT_SPAWN_ENVIRONMENT_MAX_BYTES=24*1024;const F=64;class L extends Error{constructor(r){const t=g(r);super(t.message,{cause:t}),this.name="PlaywrightConsumerPreparationError"}}export const playwrightAdapterDependencies={createInvocationEnvironment:j,removeOwnedDirectory:or,writeTaskWorkspace:Q};const er=new Set(["EBUSY","ENOTEMPTY","EPERM"]),ir=5;async function or(e){for(let r=0;;r+=1)try{await u.rm(e,{force:!0,recursive:!0});return}catch(t){const i=t instanceof Error&&"code"in t?t.code:void 0;if(r===ir||typeof i!="string"||!er.has(i))throw t;await $((r+1)*100)}}export async function executePlaywrightEngine(e,r,t,i=playwrightAdapterDependencies){const o=await nr(e,r,i,t);return C(e,o.tasks,y,(a,s)=>ar(e,o,a,s),pr(e,o.installation))}async function nr(e,r,t,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(()=>t.removeOwnedDirectory(n));const l=await t.createInvocationEnvironment(n);i.defer(()=>t.removeOwnedDirectory(l.temporaryDirectory));const p=[];for(const[h,c]of e.tasks.entries()){const I=V(e,c),v=`${h.toString().padStart(3,"0")}-${S(c.id)}`,_=Y.join(n,v),f=await D(`${v}-`,{parentDir:s});try{const m=lr(e,c.serviceName),E=await ur(()=>H(o,c.file,"Playwright task file")),w=await t.writeTaskWorkspace({artifactDirectory:f,baseUrl:m,browserFile:r.browserExecutable,databases:e.target.databases,endpoints:e.target.endpoints,handles:e.target.handles,inputs:I,invocationId:e.invocationId,managedModuleEntrypoints:{authoring:r.authoringEntrypoints.authoring,cli:r.family.cli,environment:r.authoringEntrypoints.environment,managedTest:r.authoringEntrypoints.managedTest,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 t.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 yr([_,f],t)).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 ar(e,r,t,i){const a={...await rr(e.workspace.cacheDirectory,t.inputs.environment),...sr(e,r,t)};try{k(a)}catch{delete a.NODE_COMPILE_CACHE}const s=t.task.deadlineMs-Date.now();if(s<=0)return T(t.task.id,"failed",i,W(),U(t.task.timeoutMs,y));const n=await J({args:[r.installation.family.cli,"test","--grep",X(t.prepared.sourceFile),"--config",t.prepared.workspace.configFile],cwd:r.rootDirectory,displayLabel:"Playwright",env:a,executable:process.execPath,signal:e.signal,timeoutMs:s});let l;try{l=await Z({identity:{engineId:"playwright",invocationId:e.invocationId,taskId:t.task.id,sourceFile:t.prepared.sourceFile},artifactDirectory:t.artifactDirectory,outputDirectory:t.prepared.workspace.outputDirectory,reportFile:t.prepared.workspace.reportFile,reportersFinishedFile:t.prepared.workspace.reportersFinishedFile,rootDirectory:r.rootDirectory,sourceFile:t.prepared.sourceFile,stderr:n.stderr,stdout:n.stdout})}catch(h){return T(t.task.id,"failed",i,N(n),playwrightEvidenceCollectionFailure(n,h))}const p=classifyPlaywrightProcessResult(n,l,t.task.timeoutMs,K(e.signal.reason));return T(t.task.id,p.status,i,l.evidence,p.failure)}export function classifyPlaywrightProcessResult(e,r,t,i){const o=A(e,t,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,...M(e)}}}}const s=r.attachmentFailures.at(0);return s!==void 0?{failure:s,status:"failed"}:e.exitCode!==0&&(r.reportError===null||!r.reportersFinished)?b(e,y,R(e.stderr)??R(e.stdout)):r.reportError!==null?{failure:{...P(r.reportError,"PLAYWRIGHT_REPORT_INVALID"),classification:"infra"},status:"failed"}:r.report}export function playwrightEvidenceCollectionFailure(e,r){const t=g(r);return x(e,y,t)??{...P(t,"PLAYWRIGHT_EVIDENCE_FAILED"),classification:"infra"}}function sr(e,r,t){const i={...r.invocationEnvironment},o=t.task,a=t.prepared.workspace.inputBootstrapFile,s=t.prepared.workspace.resolutionBootstrapFile;return Object.assign(i,{...t.prepared.baseUrl===null?{}:{BASE_URL:t.prepared.baseUrl},...tr,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: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=cr(s,a),k(i),i}function cr(e,r){return["--experimental-strip-types",`--require=${JSON.stringify(e)}`,`--import=${JSON.stringify(G(r).href)}`].join(" ")}export function playwrightSpawnEnvironmentBytes(e){return Object.entries(e).reduce((r,[t,i])=>r+(process.platform==="win32"?(t.length+i.length+2)*2:Buffer.byteLength(t,"utf8")+Buffer.byteLength(i,"utf8")+2),0)}function k(e){if(Object.keys(e).length>F)throw new Error(`Playwright physical spawn environment exceeds its ${F.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 lr(e,r){const t=e.target.baseUrl;if(t===null)return null;if(typeof t!="string"||t.trim().length===0)throw new Error(`Playwright task service ${r} endpoint must be a non-empty URL`);let i;try{i=new URL(t)}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 pr(e,r){return{...q({engineId:"playwright",installationKind:"root-playwright-family",profile:e.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(e,r){if(r.length===0)return{...P(e,"PLAYWRIGHT_PREPARE_FAILED"),classification:e instanceof L||e instanceof z?"product":"infra"};const t=g(e);return{classification:"infra",code:"PLAYWRIGHT_PREPARE_CLEANUP_FAILED",details:{cleanupFailures:r.map(i=>i.message),preparationFailure:t.message},message:`Playwright task preparation failed and its partial workspace could not be removed: ${r.map(i=>i.message).join("; ")}`,...t.stack===void 0?{}:{stack:t.stack}}}async function ur(e){try{return await e()}catch(r){throw new L(r)}}async function yr(e,r){const t=[];for(const i of e)try{await r.removeOwnedDirectory(i)}catch(o){t.push({directory:i,error:g(o)})}return t}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{ownedRuntimeInputModuleUrl as
|
|
1
|
+
import{ownedRuntimeInputModuleUrl as b}from"../../runtime-input-file.js";import P from"node:crypto";import s from"node:fs/promises";import o from"node:path";import{fileURLToPath as j,pathToFileURL as y}from"node:url";import{createManagedNodeModuleResolution as v}from"../../managed-module-resolution.js";import{materializePlaywrightRuntimeInput as T,playwrightRuntimeInputBootstrapSource as O}from"../runtime/runtime-input.js";import{nativeReporterFile as k}from"../../adapters/native-report.js";import{requireConsumerFile as S}from"../../adapters/workspace.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 async function writePlaywrightTaskWorkspace(e){let r;try{D(e.inputs.supportModules),r=await I(e.rootDirectory,e.inputs)}catch(w){throw new PlaywrightConsumerConfigurationError(w)}await s.mkdir(e.taskDirectory,{recursive:!0}),await s.mkdir(e.artifactDirectory,{recursive:!0});const t=P.randomUUID(),n=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`),d=o.join(e.taskDirectory,"testkit-playwright-report.json"),g=o.join(e.taskDirectory,"testkit-playwright-reporters-finished"),p=o.join(e.artifactDirectory,"playwright-output");await s.mkdir(p);const c=o.join(e.artifactDirectory,"playwright-attachments");await s.mkdir(c,{mode:448,recursive:!1});const u=await T({baseUrl:e.baseUrl,databases:e.databases,directory:e.taskDirectory,endpoints:e.endpoints,handles:e.handles,inputs:e.inputs,invocationId:e.invocationId,orchestration:e.orchestration,taskId:e.taskId}),f=k("playwright");return await h(l,playwrightResolutionBootstrapSource(e.managedModuleEntrypoints,{consumerRoots:[e.rootDirectory],trustedImporterFiles:[n,a],trustedInternalEntrypoints:[e.moduleFile,f,j(b)]})),await h(a,O({digest:u.digest,file:u.file,sizeBytes:u.sizeBytes,invocationId:e.invocationId,taskId:e.taskId})),await h(n,x({baseConfigFile:r,baseUrl:e.baseUrl,browserFile:e.browserFile,inputBootstrapFile:a,outputDirectory:p,reporterFile:f,report:{attachmentDirectory:c,identity:{engineId:"playwright",invocationId:e.invocationId,taskId:e.taskId,sourceFile:e.sourceFile},reportFile:d,reportersFinishedFile:g,workspaceDirectory:e.taskDirectory},retries:typeof e.inputs.configuration?.retries=="number"?e.inputs.configuration.retries:null,rootDirectory:e.rootDirectory,sourceFile:e.sourceFile,testkitPackageDirectory:await s.realpath(o.dirname(e.managedModuleEntrypoints.rootPackageJson))})),{attachmentDirectory:c,configFile:n,inputBootstrapFile:a,outputDirectory:p,reportFile:d,reportersFinishedFile:g,resolutionBootstrapFile:l}}export function playwrightResolutionBootstrapSource(e,r){const t={authoring:i(e.authoring,"authoring"),cli:i(e.cli,"cli"),environment:i(e.environment,"environment"),managedTest:i(e.managedTest,"managedTest"),playwright:i(e.playwright,"playwright"),playwrightCore:i(e.playwrightCore,"playwrightCore"),playwrightTest:i(e.playwrightTest,"playwrightTest"),root:i(e.root,"root"),rootPackageJson:i(e.rootPackageJson,"rootPackageJson")},n={"@playwright/test":t.playwrightTest,playwright:t.playwright,"playwright-core":t.playwrightCore};return v({consumerRoots:r.consumerRoots,direct:{"@gfdelarue/testkit":t.root,"@gfdelarue/testkit/environment":t.environment,"@gfdelarue/testkit/package.json":t.rootPackageJson,"@gfdelarue/testkit/playwright":t.authoring,"@playwright/test":t.managedTest,"playwright/test":t.managedTest},directBridgeRoots:[o.dirname(t.root)],engineLabel:"Playwright",forbiddenConsumerPackagePrefixes:[],packageAnchors:n,reservedPackageNames:["@gfdelarue/testkit"],stateSymbol:"@gfdelarue/testkit/playwright-resolution-hooks/v1",trustedImporterFiles:r.trustedImporterFiles,trustedInternalEntrypoints:r.trustedInternalEntrypoints,verifiedParentlessPackageFiles:{playwright:["lib/loader/loaderProcessEntry.js","lib/worker/workerProcessEntry.js"]},verifiedProcessEntrypoints:[t.cli]}).bootstrapSource}function i(e,r){if(!o.isAbsolute(e))throw new Error(`Resolved Playwright installation ${r} entrypoint must be absolute`);return e}function x(e){return`
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
|
|
5
|
-
const OPTIONS = ${JSON.stringify({baseConfigUrl:e.baseConfigFile===null?null:y(e.baseConfigFile).href,baseUrl:e.baseUrl,browserFile:e.browserFile,exactTaskMatchSource:playwrightExactFileFilter(e.sourceFile),originalConfigDirectory:e.baseConfigFile===null?e.rootDirectory:o.dirname(e.baseConfigFile),originalConfigFile:e.baseConfigFile??o.join(e.rootDirectory,"__testkit_playwright_config__.mjs"),inputBootstrapUrl:y(e.inputBootstrapFile).href,outputDirectory:e.outputDirectory,report:e.report,reporterFile:e.reporterFile,rootDirectory:e.rootDirectory,sourceFile:e.sourceFile})};
|
|
5
|
+
const OPTIONS = ${JSON.stringify({baseConfigUrl:e.baseConfigFile===null?null:y(e.baseConfigFile).href,baseUrl:e.baseUrl,browserFile:e.browserFile,exactTaskMatchSource:playwrightExactFileFilter(e.sourceFile),originalConfigDirectory:e.baseConfigFile===null?e.rootDirectory:o.dirname(e.baseConfigFile),originalConfigFile:e.baseConfigFile??o.join(e.rootDirectory,"__testkit_playwright_config__.mjs"),inputBootstrapUrl:y(e.inputBootstrapFile).href,outputDirectory:e.outputDirectory,report:e.report,reporterFile:e.reporterFile,retries:e.retries,rootDirectory:e.rootDirectory,sourceFile:e.sourceFile,testkitPackageMatchSource:`^${m(e.testkitPackageDirectory)}[\\\\/]`})};
|
|
6
6
|
const inputBootstrap = await import(OPTIONS.inputBootstrapUrl);
|
|
7
7
|
if (typeof inputBootstrap.installPlaywrightRuntimeInput !== "function")
|
|
8
8
|
throw new Error("Managed Playwright runtime-input bootstrap is invalid");
|
|
@@ -16,6 +16,7 @@ if (process.env.PW_TEST_REPORTER !== undefined)
|
|
|
16
16
|
const resolver = createRequire(OPTIONS.originalConfigFile);
|
|
17
17
|
const builtInReporters = new Set(["list", "line", "dot", "json", "junit", "null", "github", "html", "blob"]);
|
|
18
18
|
const exactTaskMatch = new RegExp(OPTIONS.exactTaskMatchSource, "u");
|
|
19
|
+
const baseExternal = base.build?.external;
|
|
19
20
|
|
|
20
21
|
function absolutePath(value) {
|
|
21
22
|
return typeof value === "string" && value.length > 0
|
|
@@ -54,7 +55,7 @@ function normalizeExpect(expect) {
|
|
|
54
55
|
};
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
function normalizeUse(use, label, inheritedSandbox) {
|
|
58
|
+
function normalizeUse(use, label, inheritedSandbox, inheritedTimeZone) {
|
|
58
59
|
const value = use && typeof use === "object" && !Array.isArray(use) ? use : {};
|
|
59
60
|
const launchOptions = value.launchOptions && typeof value.launchOptions === "object" ? value.launchOptions : {};
|
|
60
61
|
if (value.browserName !== undefined && value.browserName !== "chromium")
|
|
@@ -67,6 +68,9 @@ function normalizeUse(use, label, inheritedSandbox) {
|
|
|
67
68
|
...value,
|
|
68
69
|
browserName: "chromium",
|
|
69
70
|
...(OPTIONS.baseUrl === null ? {} : { baseURL: OPTIONS.baseUrl }),
|
|
71
|
+
...(value.timezoneId === undefined && inheritedTimeZone === undefined && knownTimeZone(process.env.TZ)
|
|
72
|
+
? { timezoneId: process.env.TZ }
|
|
73
|
+
: {}),
|
|
70
74
|
launchOptions: {
|
|
71
75
|
...launchOptions,
|
|
72
76
|
chromiumSandbox: launchOptions.chromiumSandbox ?? inheritedSandbox ?? true,
|
|
@@ -75,6 +79,16 @@ function normalizeUse(use, label, inheritedSandbox) {
|
|
|
75
79
|
};
|
|
76
80
|
}
|
|
77
81
|
|
|
82
|
+
function knownTimeZone(zone) {
|
|
83
|
+
if (!zone) return false;
|
|
84
|
+
try {
|
|
85
|
+
new Intl.DateTimeFormat("en-US", { timeZone: zone });
|
|
86
|
+
return true;
|
|
87
|
+
} catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
78
92
|
function normalizeSetups(value) {
|
|
79
93
|
if (value === undefined) return [];
|
|
80
94
|
return (Array.isArray(value) ? value : [value]).map(absolutePath);
|
|
@@ -136,8 +150,9 @@ function normalizeProject(project, index, supportNames) {
|
|
|
136
150
|
snapshotPathTemplate: absoluteTemplate(project.snapshotPathTemplate),
|
|
137
151
|
}),
|
|
138
152
|
expect: normalizeExpect(project.expect),
|
|
153
|
+
...(OPTIONS.retries === null ? {} : { retries: OPTIONS.retries }),
|
|
139
154
|
...(isSupportProject ? {} : { testMatch: exactTaskMatch }),
|
|
140
|
-
use: normalizeUse(project.use, "Playwright config.projects[" + index + "].use", baseSandbox),
|
|
155
|
+
use: normalizeUse(project.use, "Playwright config.projects[" + index + "].use", baseSandbox, base.use?.timezoneId),
|
|
141
156
|
outputDir: OPTIONS.outputDirectory,
|
|
142
157
|
};
|
|
143
158
|
}
|
|
@@ -153,6 +168,7 @@ const supportNames = projects === null ? new Set() : supportProjectNames(project
|
|
|
153
168
|
|
|
154
169
|
export default {
|
|
155
170
|
...base,
|
|
171
|
+
...(OPTIONS.retries === null ? {} : { retries: OPTIONS.retries }),
|
|
156
172
|
testDir: base.testDir === undefined ? OPTIONS.rootDirectory : absolutePath(base.testDir),
|
|
157
173
|
...(projects === null ? { testMatch: exactTaskMatch } : {}),
|
|
158
174
|
...(base.snapshotDir === undefined ? {} : { snapshotDir: absolutePath(base.snapshotDir) }),
|
|
@@ -167,6 +183,13 @@ export default {
|
|
|
167
183
|
}),
|
|
168
184
|
globalSetup: normalizeSetups(base.globalSetup),
|
|
169
185
|
globalTeardown: normalizeSetups(base.globalTeardown),
|
|
186
|
+
build: {
|
|
187
|
+
...base.build,
|
|
188
|
+
external: [
|
|
189
|
+
...(baseExternal === undefined ? [] : Array.isArray(baseExternal) ? baseExternal : [baseExternal]),
|
|
190
|
+
new RegExp(OPTIONS.testkitPackageMatchSource, "u"),
|
|
191
|
+
],
|
|
192
|
+
},
|
|
170
193
|
outputDir: OPTIONS.outputDirectory,
|
|
171
194
|
preserveOutput: "always",
|
|
172
195
|
reporter: normalizeReporters(base.reporter),
|
|
@@ -174,4 +197,4 @@ export default {
|
|
|
174
197
|
fullyParallel: false,
|
|
175
198
|
workers: 1,
|
|
176
199
|
};
|
|
177
|
-
`}export function playwrightExactFileFilter(e){return`^${e.split(/[\\/]+/u).map(r=>r.replace(/[.*+?^${}()|[\]\\]/gu,"\\$&")).join("[\\\\/]")}
|
|
200
|
+
`}export function playwrightExactFileFilter(e){return`^${m(e)}$`}function m(e){return e.split(/[\\/]+/u).map(r=>r.replace(/[.*+?^${}()|[\]\\]/gu,"\\$&")).join("[\\\\/]")}export function playwrightFileSuiteGrep(e){const r=e.split(/[\\/]+/u).filter(a=>a!=="").map(a=>a.replace(/[.*+?^${}()|[\]\\]/gu,"\\$&")),t=r.pop()??"";return`(?:^| )(?:\\.\\.[\\\\/])*${r.reduce((a,l)=>`(?:${a}${l}[\\\\/])?`,"")}${t}(?: |$)`}async function I(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 S(e,t,"supportModules.playwrightConfig")}function D(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 h(e,r){const t=await s.open(e,"wx",384);try{await t.writeFile(r,"utf8")}finally{await t.close()}}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{toError as ht}from"../../../kernel/error-serialization.js";import{isPathWithin as D}from"../../../process/files/paths.js";import
|
|
2
|
-
${p}`},status:"failed"}}return o.length>0?{failure:null,status:o.every(p=>p.status==="skipped")?"skipped":"passed"}:{failure:{code:"PLAYWRIGHT_NO_TESTS",details:{file:U(e,r),reportedSpecs:t.specs.slice(0,50),stats:t.stats},message:"Selected Playwright task did not execute any tests"},status:"failed"}}async function Wt(t){try{return(await T.lstat(t)).isFile()}catch{return!1}}function ot(t,e){return w.relative(w.normalize(t),e)===""}function Ut(t,e){const r=S(t,"Playwright native report result"),a=Ct.find(y=>y===r.status);if(a===void 0)throw new Error("Playwright native report status is invalid");if(typeof r.failOnFlakyTests!="boolean")throw new Error("Playwright native report failOnFlakyTests must be a boolean");const n=r.failOnFlakyTests,s=qt(r.stats),o=G(r.errors,"errors"),h=o.length,u=rt(r.environmentFailure,"Playwright native report environment failure"),p=u===null?[]:[u];let f=u!==null;const i=[],l=[],m=[],E=new Set,c=new Map,g={expected:0,flaky:0,skipped:0,unexpected:0};for(const y of A(r.specs,"specs")){const d=S(y,"Playwright native report spec"),R=x(d.id,"Playwright native spec.id");if(E.has(R))throw new Error("Playwright native report contains a duplicate test identity");E.add(R);const k=x(d.title,"Playwright native spec.title"),v=G(d.parents,"spec.parents");v.length>0&&(v[0]=et(v[0]));const j=et(x(d.file,"Playwright native spec.file")),O=x(d.sourceFile,"Playwright native spec.sourceFile");if(!w.isAbsolute(O)&&!w.win32.isAbsolute(O))throw new Error("Playwright native spec.sourceFile must be absolute");const dt=B(d.projectName,"spec.projectName"),P=d.status;if(P!=="expected"&&P!=="flaky"&&P!=="skipped"&&P!=="unexpected")throw new Error("Playwright native spec.status is invalid");const K=Dt.find(ft=>ft===d.expectedStatus);if(K===void 0)throw new Error("Playwright native spec.expectedStatus is invalid");g[P]+=1;const V=G(d.failureMessages,"spec.failureMessages"),J=d.failureStack===null?null:B(d.failureStack,"spec.failureStack"),N=tt(d.line,"Playwright native spec.line"),M=tt(d.column,"Playwright native spec.column"),C=ot(e,O),$=rt(d.environmentFailure,"Playwright native spec environment failure");$!==null&&(f=!0),C&&$!==null&&p.push($),P==="unexpected"||P==="flaky"&&n?(o.push(...V),m.push({...$===null?{}:{classification:"infra"},kind:C?"playwright-spec":"playwright-other-file-spec",key:[...v.slice(1),k].filter(Boolean).join(" "),title:k,count:1,message:V[0]??`Playwright reported "${k}" as an unexpected failure`,...v.length===0?{}:{suitePath:v},location:{path:j,...N===null?{}:{line:N},...M===null?{}:{column:M}},...J===null?{}:{stack:J}})):C&&P==="skipped"&&K!=="skipped"&&m.push({kind:"playwright-not-run",key:[...v.slice(1),k].filter(Boolean).join(" "),title:k,message:`Playwright did not run "${k}", which was expected to run`,...v.length===0?{}:{suitePath:v},location:{path:j,...N===null?{}:{line:N},...M===null?{}:{column:M}}}),l.push(O);const Q=b(d.retries,"Playwright native spec.retries");c.set(R,{retry:Q,status:zt[P],title:[...v.slice(1),k].join(" \u203A ")}),i.push({durationMs:q(d.durationMs,"Playwright native spec.durationMs"),retries:Q,file:j,projectName:dt,status:P,title:[...v,k].filter(Boolean).join(" > ")})}for(const y of["expected","flaky","skipped","unexpected"])if(s[y]!==g[y])throw new Error("Playwright native report stats do not match its cases");const F=A(r.logs,"logs").map(y=>{const d=S(y,"Playwright native report log");if(d.stream!=="stdout"&&d.stream!=="stderr")throw new Error("Playwright native report log stream is invalid");return{stream:d.stream,text:B(d.text,"log.text")}});return{attachments:A(r.attachments,"attachments").map((y,d)=>Xt(y,d,E)),carriesEnvironmentFailures:f,environmentFailures:p,failOnFlakyTests:n,failureDetails:m,failureMessages:o,logs:te(F),reportErrors:h,runStatus:a,sourceFiles:l,specs:i,stats:s,tests:c}}function Yt(t){const e=S(t,"Testkit native Playwright report"),r=ct(S(e.result,"Playwright native report result")),a=A(r.specs,"specs").map(n=>ct(S(n,"Playwright native report spec")));return JSON.stringify({...e,result:{...r,specs:a}})}function ct(t){return Object.fromEntries(Object.entries(t).filter(([e])=>e!=="environmentFailure"))}function A(t,e){if(!Array.isArray(t))throw new Error(`Playwright native report ${e} must be an array`);return t}function G(t,e){return A(t,e).map(r=>B(r,e))}function B(t,e){if(typeof t!="string")throw new Error(`Playwright native report ${e} must be a string`);return t}function Xt(t,e,r){const a=S(t,`Playwright attachment[${e.toString()}]`),n=x(a.specId,"Playwright attachment.specId");if(!r.has(n))throw new Error("Playwright native attachment references an unknown test identity");const s=W(a.path),o=a.error===void 0?void 0:Gt(a.error);if(a.body!==void 0||s===null&&o===void 0)throw new Error("Playwright attachment must contain a path or an error");return{contentType:W(a.contentType)??"application/octet-stream",name:W(a.name)??`attachment-${e.toString()}`,specId:n,...s===null?{}:{path:s},...o===void 0?{}:{error:o},retry:b(a.retry,"Playwright attachment.retry"),specTitle:x(a.specTitle,"Playwright attachment.specTitle")}}function Gt(t){const e=S(t,"Playwright attachment.error");if(e.kind==="io")return{kind:"io",message:x(e.message,"Playwright attachment.error.message")};const r=e.kind;if(r!=="individual"&&r!=="total")throw new Error("Playwright attachment.error has an invalid kind");const a=r==="individual"?nt:At,n=b(e.observed,"Playwright attachment.error.observed");if(e.limit!==a||n<=a)throw new Error("Playwright attachment.error has an invalid limit");return{kind:r,limit:a,observed:n}}function L(t,e){const{name:r,specId:a,specTitle:n,retry:s}=t,o=e.kind==="io"?e.message:`exceeds its ${e.limit.toString()} byte ${e.kind} limit (observed ${e.observed.toString()} bytes)`;return{code:e.kind==="io"?"PLAYWRIGHT_ATTACHMENT_FAILED":"PLAYWRIGHT_ATTACHMENT_LIMIT",...e.kind==="io"?{classification:"infra"}:{},details:{name:r,specId:a,specTitle:n,retry:s,...e},message:`Playwright attachment "${r}" (${n}, retry ${s.toString()}): ${o}`}}function lt(t){return t.name===St&&t.error===void 0}async function Kt(t,e,r,a,n,s){const o=[],h=new Set,u=[];for(const i of n.attachments.filter(lt))try{const l=await ut(i.path,r,t,s);h.add(l.file);const m=n.tests.get(i.specId);if(m===void 0||i.retry!==m.retry)continue;const{contents:E}=await yt(l.file,Ft,"Browser network record",s,l.identity);u.push({title:m.title,status:m.status,retry:m.retry,...xt(E)})}catch(l){o.push(L(i,it(l)))}if(u.length===0)return{artifacts:[],failures:o,sourcePaths:h};const p=_t(a,u),f=[];for(const[i,l,m,E]of[["browser-network",p.json,"application/json",kt],["browser-network-report",p.markdown,"text/markdown",bt]]){const c=z(Buffer.from(l,"utf8")),g=await H(e,`${i}${m==="text/markdown"?".md":".json"}`,c.contents);f.push(I(g,c,m,E,i,p.summary))}return{artifacts:f,failures:o,sourcePaths:h}}async function Vt(t,e,r,a,n){const s=[],o=[],h=new Set;let u=0;const p=w.join(e,"attachments");a.length>0&&await T.mkdir(p,{mode:448,recursive:!1});for(const[f,i]of a.entries())try{if(i.error!==void 0){i.path!==void 0&&h.add(w.resolve(r,i.path)),o.push(L(i,i.error));continue}const l=await ut(i.path,r,t,n);h.add(l.file);const m=b(Number(l.identity.size),"Playwright attachment size"),E=Rt(m,u+m);if(E!==null){o.push(L(i,E));continue}const c=await Z(l.file,nt,"Playwright attachment",n,l.identity,p,`${f.toString().padStart(4,"0")}-${pt(i.name)}${ee(i)}`);u+=c.identity.sizeBytes,s.push({path:c.filePath,contentType:i.contentType,kind:"engine.playwright-attachment",name:i.name,summary:`${i.specTitle} retry ${i.retry.toString()}`,metadata:{...c.identity,specId:i.specId,specTitle:i.specTitle,retry:i.retry}})}catch(l){o.push(L(i,it(l)))}return{artifacts:s,failures:o,sourcePaths:h}}async function Jt(t,e,r,a,n){try{const s=await n.lstat(r);if(!s.isDirectory()||s.isSymbolicLink())throw new Error("Playwright output directory must be a non-symbolic-link directory");const o=await T.realpath(r);if(!D(t,o))throw new Error("Playwright output directory escaped the artifact root");const h=await Qt(o,a,n),u=[],p=w.join(e,"output");h.candidates.length>0&&await T.mkdir(p,{mode:448,recursive:!1});for(const[f,i]of h.candidates.entries()){const l=await Z(i.file,Y,"Playwright output file",n,i.identity,p,`${f.toString().padStart(5,"0")}-${pt(w.basename(i.file))}${ie(i.file)}`);u.push(I(l.filePath,l.identity,re(i.file),"engine.playwright-output",i.relativePath,"Playwright output"))}return{artifacts:u,bytes:h.bytes,entries:h.entries}}catch(s){if(gt(s))return{artifacts:[],bytes:0,entries:0};throw s}}async function Qt(t,e,r){const a=[];let n=0,s=0;const o=[{depth:0,directory:t}];for(;o.length>0;){const h=o.pop();if(h===void 0)continue;if(h.depth>at)throw new Error(`Playwright output exceeds the ${at.toString()} level directory depth limit`);const u=h.directory,p=await T.readdir(u,{withFileTypes:!0});p.sort((f,i)=>f.name.localeCompare(i.name));for(const f of p){if(s+=1,s>st)throw new X({bytes:n,entries:s,kind:"entries",limit:st,observed:s});const i=w.join(u,f.name),l=await r.lstat(i);if(l.isSymbolicLink())throw new Error("Playwright output must not contain symbolic links");if(l.isDirectory()){o.push({depth:h.depth+1,directory:i});continue}if(!l.isFile())continue;if(l.size<0n||l.size>BigInt(Number.MAX_SAFE_INTEGER))throw new Error("Playwright output file has an invalid size");const m=Number(l.size);if(n+=m,!Number.isSafeInteger(n)||n>Y)throw new X({bytes:n,entries:s,kind:"bytes",limit:Y,observed:n});e.has(i)||a.push({file:i,identity:l,relativePath:U(t,i)})}}return{bytes:n,candidates:a,entries:s}}function Zt(t){return t.kind==="bytes"?`Playwright output exceeds its ${t.limit.toString()} byte aggregate limit (observed ${t.observed.toString()} bytes)`:`Playwright output exceeds its ${t.limit.toString()} entry limit (observed ${t.observed.toString()} entries)`}async function ut(t,e,r,a){if(t===void 0)throw new Error("Playwright path attachment did not declare a path");const n=w.resolve(e,t),s=await a.lstat(n);if(!s.isFile()||s.isSymbolicLink())throw new Error("Playwright attachment path must be a regular non-symbolic-link file");const o=await T.realpath(n);if(!D(e,o)&&!D(r,o))throw new Error("Playwright attachment path must stay within the consumer or artifact root");return{file:o,identity:s}}function qt(t){const e=S(t,"Playwright report.stats");return{expected:b(e.expected,"Playwright stats.expected"),unexpected:b(e.unexpected,"Playwright stats.unexpected"),flaky:b(e.flaky,"Playwright stats.flaky"),skipped:b(e.skipped,"Playwright stats.skipped"),duration:q(e.duration,"Playwright stats.duration")}}function te(t){let e=Nt;const r=[];for(const a of t){if(e<=0)break;const s=Buffer.from(a.text,"utf8").subarray(0,e).toString("utf8");s.length>0&&r.push({...a,text:s}),e-=Buffer.byteLength(s)}return e<=0&&r.push({stream:"engine",text:"Playwright report log evidence was truncated at its bounded limit"}),r}function ee(t){if(t.path!==void 0){const e=w.extname(t.path);if(/^\.[a-z0-9]{1,12}$/iu.test(e))return e}return t.contentType==="application/json"?".json":t.contentType.startsWith("text/")?".txt":t.contentType==="image/png"?".png":t.contentType==="image/jpeg"?".jpg":".bin"}function re(t){const e=w.extname(t).toLowerCase();return e===".json"?"application/json":e===".zip"?"application/zip":e===".png"?"image/png":e===".jpg"||e===".jpeg"?"image/jpeg":e===".webm"?"video/webm":[".log",".txt",".md"].includes(e)?"text/plain; charset=utf-8":"application/octet-stream"}function ie(t){const e=w.extname(t).toLowerCase();return/^\.[a-z0-9]{1,12}$/u.test(e)?e:".bin"}function pt(t){const e=t.trim().toLowerCase().replace(/[^a-z0-9._-]+/gu,"-").replace(/^-+|-+$/gu,"").slice(0,80);return e.length>0?e:"attachment"}
|
|
1
|
+
import{toError as ht}from"../../../kernel/error-serialization.js";import{isPathWithin as D}from"../../../process/files/paths.js";import k from"node:fs/promises";import w from"node:path";import{evidenceArtifact as I,evidenceFileAccess as mt,readStableEvidenceFile as yt,readStableJsonFile as wt,snapshotStableEvidenceFile as Z,stableEvidenceFromBuffer as z,writeOwnedEvidenceFile as H,isMissingFileError as gt}from"../../adapters/evidence-files.js";import{materializePlaywrightFailureDetails as Et}from"./failure-details.js";import{nonNegativeInteger as F,nonNegativeNumber as q,optionalNonEmptyString as W,optionalNonNegativeInteger as tt,portableRelativePath as U,portableReportPath as et,requireNonEmptyString as _,requireRecord as b}from"./evidence-validation.js";import{readNativeReport as vt}from"../../adapters/native-report.js";import{environmentValueMissingFailure as Pt,parseEnvironmentValueMissingFailure as rt,readEnvironmentValueMissingFailure as Tt}from"../../contracts/environment-values.js";import{BROWSER_NETWORK_ATTACHMENT as St}from"../runtime/browser-network.js";import{BROWSER_NETWORK_ARTIFACT_KIND as kt,BROWSER_NETWORK_REPORT_KIND as bt,MAX_BROWSER_NETWORK_RECORD_BYTES as Ft,readBrowserNetworkRecord as xt,renderBrowserNetwork as _t}from"./browser-network-evidence.js";import{attachmentLimitError as Rt,attachmentIoError as it,MAX_ATTACHMENT_BYTES as nt,MAX_TOTAL_ATTACHMENT_BYTES as At}from"./attachments.js";const Ot=64*1024*1024,st=1e4,Y=512*1024*1024,Nt=2*1024*1024,at=128,Mt=".testkit-playwright-evidence",$t=/(?:^|\n)(?:Error:\s*)?No tests found(?:\.|\n|$)/u,It=/browserType\.launch(?:PersistentContext)?:/u,Bt=/browserType\.launch(?:PersistentContext|Server)?:|Failed to launch browser/u,Lt=/Chromium sandboxing failed!|No usable sandbox!|crbug\.com\/(?:357670|638180)/u,jt="Testkit starts managed Chromium with its sandbox on, and this host can't start it. To run without the sandbox here, set use.launchOptions.chromiumSandbox to false in playwright.config.ts, or in one file with test.use.",Ct=["failed","interrupted","passed","timedout"],Dt=["failed","interrupted","passed","skipped","timedOut"],zt={expected:"passed",flaky:"flaky",skipped:"skipped",unexpected:"failed"};class X extends Error{limit;constructor(e){super(Zt(e)),this.limit=e,this.name="PlaywrightOutputArtifactLimitError"}}export async function collectPlaywrightEvidence(t,e=mt){await k.mkdir(t.artifactDirectory,{recursive:!0});const r=await k.realpath(t.artifactDirectory),a=await k.realpath(t.rootDirectory),n=w.join(r,Mt);await k.mkdir(n,{mode:448,recursive:!1});const s=[],o=[];for(const u of["stdout","stderr"]){const P=t[u];if(P.length===0)continue;s.push({stream:u,text:P});const R=z(Buffer.from(P,"utf8")),y=await H(n,`${u}.log`,R.contents);o.push(I(y,R,"text/plain; charset=utf-8","engine.process-output",`playwright-${u}`,`${R.sizeBytes.toString()} bytes`))}let h=null,l=null,p=new Set,f=[];try{const u=await wt(t.reportFile,Ot,"Testkit native Playwright report",e);if(u.file===null)h=new Error("Playwright did not produce its Testkit native report");else{l=Ut(vt(u.value,t.identity).result,t.sourceFile);const P=l.carriesEnvironmentFailures?z(Buffer.from(Yt(u.value))):u.file,R=await H(n,"testkit-playwright-report.json",P.contents);o.push(I(R,P,"application/json","engine.testkit-playwright-report","testkit-playwright-report","Testkit native Playwright report")),s.push(...l.logs);const y=await Et(n,l.failureDetails);y!==null&&o.push(y);const d=await Kt(r,n,a,U(a,t.sourceFile),l,e),x=await Vt(r,n,a,l.attachments.filter(T=>!lt(T)),e);o.push(...d.artifacts,...x.artifacts),p=new Set([...d.sourcePaths,...x.sourcePaths]),f=[...d.failures,...x.failures]}}catch(u){h=ht(u)}let i={artifacts:[],bytes:0,entries:0},c=null;try{i=await Jt(r,n,t.outputDirectory,p,e),o.push(...i.artifacts)}catch(u){if(!(u instanceof X))throw u;c=u.limit,s.push({stream:"engine",text:u.message})}const m=await Wt(t.reportersFinishedFile),g=Ht(l,a,t.sourceFile,f.length,m),E=c??i;return{attachmentFailures:f,evidence:{artifacts:o,logs:s,metrics:[...Object.entries(l?.stats??{}).map(([u,P])=>({name:u==="duration"?"playwright.duration":`playwright.tests.${u}`,unit:u==="duration"?"milliseconds":"tests",value:P})),...["bytes","entries"].map(u=>({name:`playwright.output.${u}`,unit:u,value:E[u]}))],...l===null?{}:{native:{playwright:{attachmentFailures:f.map(u=>({...u})),specs:l.specs,stats:l.stats}}}},outputLimit:c,report:g,reportError:h,reportersFinished:m}}function Ht(t,e,r,a,n){if(t===null)return{failure:null,status:"failed"};const s=t.environmentFailures.at(0);if(s!==void 0)return{failure:Pt(t.environmentFailures.flatMap(p=>Tt(p)??[]),s.message),status:"failed"};const o=t.specs.filter((p,f)=>ot(r,t.sourceFiles[f])),h=t.failureMessages.length>0&&t.failureMessages.every(p=>$t.test(p)),l=t.failOnFlakyTests?t.stats.flaky:0;if(t.stats.unexpected+l>0||t.failureMessages.length>0&&(!h||o.length>0)){const p=t.failureMessages.find(f=>Bt.test(f)&&Lt.test(f));return{failure:{...p!==void 0||t.failureMessages.some(f=>It.test(f)&&/Executable doesn't exist|Host system is missing dependencies|error while loading shared libraries|Failed to launch/u.test(f))?{classification:"infra"}:{},code:"PLAYWRIGHT_SPECS_FAILED",details:{attachmentFailures:a,failOnFlakyTests:t.failOnFlakyTests,reportErrors:t.reportErrors,reportersFinished:n,runStatus:t.runStatus,stats:t.stats,specs:t.specs.slice(0,50)},message:p===void 0?t.failureMessages.at(0)??`${(t.stats.unexpected+l).toString()} Playwright test(s) failed`:`${jt}
|
|
2
|
+
${p}`},status:"failed"}}return o.length>0?{failure:null,status:o.every(p=>p.status==="skipped")?"skipped":"passed"}:{failure:{code:"PLAYWRIGHT_NO_TESTS",details:{file:U(e,r),reportedSpecs:t.specs.slice(0,50),stats:t.stats},message:"Selected Playwright task did not execute any tests"},status:"failed"}}async function Wt(t){try{return(await k.lstat(t)).isFile()}catch{return!1}}function ot(t,e){return w.relative(w.normalize(t),e)===""}function Ut(t,e){const r=b(t,"Playwright native report result"),a=Ct.find(y=>y===r.status);if(a===void 0)throw new Error("Playwright native report status is invalid");if(typeof r.failOnFlakyTests!="boolean")throw new Error("Playwright native report failOnFlakyTests must be a boolean");const n=r.failOnFlakyTests,s=qt(r.stats),o=G(r.errors,"errors"),h=o.length,l=rt(r.environmentFailure,"Playwright native report environment failure"),p=l===null?[]:[l];let f=l!==null;const i=[],c=[],m=[],g=new Set,E=new Map,u={expected:0,flaky:0,skipped:0,unexpected:0};for(const y of A(r.specs,"specs")){const d=b(y,"Playwright native report spec"),x=_(d.id,"Playwright native spec.id");if(g.has(x))throw new Error("Playwright native report contains a duplicate test identity");g.add(x);const T=_(d.title,"Playwright native spec.title"),v=G(d.parents,"spec.parents");v.length>0&&(v[0]=et(v[0]));const j=et(_(d.file,"Playwright native spec.file")),O=_(d.sourceFile,"Playwright native spec.sourceFile");if(!w.isAbsolute(O)&&!w.win32.isAbsolute(O))throw new Error("Playwright native spec.sourceFile must be absolute");const dt=B(d.projectName,"spec.projectName"),S=d.status;if(S!=="expected"&&S!=="flaky"&&S!=="skipped"&&S!=="unexpected")throw new Error("Playwright native spec.status is invalid");const K=Dt.find(ft=>ft===d.expectedStatus);if(K===void 0)throw new Error("Playwright native spec.expectedStatus is invalid");u[S]+=1;const V=G(d.failureMessages,"spec.failureMessages"),J=d.failureStack===null?null:B(d.failureStack,"spec.failureStack"),N=tt(d.line,"Playwright native spec.line"),M=tt(d.column,"Playwright native spec.column"),C=ot(e,O),$=rt(d.environmentFailure,"Playwright native spec environment failure");$!==null&&(f=!0),C&&$!==null&&p.push($),S==="unexpected"||S==="flaky"&&n?(o.push(...V),m.push({...$===null?{}:{classification:"infra"},kind:C?"playwright-spec":"playwright-other-file-spec",key:[...v.slice(1),T].filter(Boolean).join(" "),title:T,count:1,message:V[0]??`Playwright reported "${T}" as an unexpected failure`,...v.length===0?{}:{suitePath:v},location:{path:j,...N===null?{}:{line:N},...M===null?{}:{column:M}},...J===null?{}:{stack:J}})):C&&S==="skipped"&&K!=="skipped"&&m.push({kind:"playwright-not-run",key:[...v.slice(1),T].filter(Boolean).join(" "),title:T,message:`Playwright did not run "${T}", which was expected to run`,...v.length===0?{}:{suitePath:v},location:{path:j,...N===null?{}:{line:N},...M===null?{}:{column:M}}}),c.push(O);const Q=F(d.retries,"Playwright native spec.retries");E.set(x,{retry:Q,status:zt[S],title:[...v.slice(1),T].join(" \u203A ")}),i.push({durationMs:q(d.durationMs,"Playwright native spec.durationMs"),retries:Q,file:j,projectName:dt,status:S,title:[...v,T].filter(Boolean).join(" > ")})}for(const y of["expected","flaky","skipped","unexpected"])if(s[y]!==u[y])throw new Error("Playwright native report stats do not match its cases");const P=A(r.logs,"logs").map(y=>{const d=b(y,"Playwright native report log");if(d.stream!=="stdout"&&d.stream!=="stderr")throw new Error("Playwright native report log stream is invalid");return{stream:d.stream,text:B(d.text,"log.text")}});return{attachments:A(r.attachments,"attachments").map((y,d)=>Xt(y,d,g)),carriesEnvironmentFailures:f,environmentFailures:p,failOnFlakyTests:n,failureDetails:m,failureMessages:o,logs:te(P),reportErrors:h,runStatus:a,sourceFiles:c,specs:i,stats:s,tests:E}}function Yt(t){const e=b(t,"Testkit native Playwright report"),r=ct(b(e.result,"Playwright native report result")),a=A(r.specs,"specs").map(n=>ct(b(n,"Playwright native report spec")));return JSON.stringify({...e,result:{...r,specs:a}})}function ct(t){return Object.fromEntries(Object.entries(t).filter(([e])=>e!=="environmentFailure"))}function A(t,e){if(!Array.isArray(t))throw new Error(`Playwright native report ${e} must be an array`);return t}function G(t,e){return A(t,e).map(r=>B(r,e))}function B(t,e){if(typeof t!="string")throw new Error(`Playwright native report ${e} must be a string`);return t}function Xt(t,e,r){const a=b(t,`Playwright attachment[${e.toString()}]`),n=_(a.specId,"Playwright attachment.specId");if(!r.has(n))throw new Error("Playwright native attachment references an unknown test identity");const s=W(a.path),o=a.error===void 0?void 0:Gt(a.error);if(a.body!==void 0||s===null&&o===void 0)throw new Error("Playwright attachment must contain a path or an error");return{contentType:W(a.contentType)??"application/octet-stream",name:W(a.name)??`attachment-${e.toString()}`,specId:n,...s===null?{}:{path:s},...o===void 0?{}:{error:o},retry:F(a.retry,"Playwright attachment.retry"),specTitle:_(a.specTitle,"Playwright attachment.specTitle")}}function Gt(t){const e=b(t,"Playwright attachment.error");if(e.kind==="io")return{kind:"io",message:_(e.message,"Playwright attachment.error.message")};const r=e.kind;if(r!=="individual"&&r!=="total")throw new Error("Playwright attachment.error has an invalid kind");const a=r==="individual"?nt:At,n=F(e.observed,"Playwright attachment.error.observed");if(e.limit!==a||n<=a)throw new Error("Playwright attachment.error has an invalid limit");return{kind:r,limit:a,observed:n}}function L(t,e){const{name:r,specId:a,specTitle:n,retry:s}=t,o=e.kind==="io"?e.message:`exceeds its ${e.limit.toString()} byte ${e.kind} limit (observed ${e.observed.toString()} bytes)`;return{code:e.kind==="io"?"PLAYWRIGHT_ATTACHMENT_FAILED":"PLAYWRIGHT_ATTACHMENT_LIMIT",...e.kind==="io"?{classification:"infra"}:{},details:{name:r,specId:a,specTitle:n,retry:s,...e},message:`Playwright attachment "${r}" (${n}, retry ${s.toString()}): ${o}`}}function lt(t){return t.name===St&&t.error===void 0}async function Kt(t,e,r,a,n,s){const o=[],h=new Set,l=[];for(const i of n.attachments.filter(lt))try{const c=await ut(i.path,r,t,s);h.add(c.file);const m=n.tests.get(i.specId);if(m===void 0||i.retry!==m.retry)continue;const{contents:g}=await yt(c.file,Ft,"Browser network record",s,c.identity);l.push({title:m.title,status:m.status,retry:m.retry,...xt(g)})}catch(c){o.push(L(i,it(c)))}if(l.length===0)return{artifacts:[],failures:o,sourcePaths:h};const p=_t(a,l),f=[];for(const[i,c,m,g]of[["browser-network",p.json,"application/json",kt],["browser-network-report",p.markdown,"text/markdown",bt]]){const E=z(Buffer.from(c,"utf8")),u=await H(e,`${i}${m==="text/markdown"?".md":".json"}`,E.contents);f.push(I(u,E,m,g,i,p.summary))}return{artifacts:f,failures:o,sourcePaths:h}}async function Vt(t,e,r,a,n){const s=[],o=[],h=new Set;let l=0;const p=w.join(e,"attachments");a.length>0&&await k.mkdir(p,{mode:448,recursive:!1});for(const[f,i]of a.entries())try{if(i.error!==void 0){i.path!==void 0&&h.add(w.resolve(r,i.path)),o.push(L(i,i.error));continue}const c=await ut(i.path,r,t,n);h.add(c.file);const m=F(Number(c.identity.size),"Playwright attachment size"),g=Rt(m,l+m);if(g!==null){o.push(L(i,g));continue}const E=await Z(c.file,nt,"Playwright attachment",n,c.identity,p,`${f.toString().padStart(4,"0")}-${pt(i.name)}${ee(i)}`);l+=E.identity.sizeBytes,s.push({path:E.filePath,contentType:i.contentType,kind:"engine.playwright-attachment",name:i.name,summary:`${i.specTitle} retry ${i.retry.toString()}`,metadata:{...E.identity,specId:i.specId,specTitle:i.specTitle,retry:i.retry}})}catch(c){o.push(L(i,it(c)))}return{artifacts:s,failures:o,sourcePaths:h}}async function Jt(t,e,r,a,n){try{const s=await n.lstat(r);if(!s.isDirectory()||s.isSymbolicLink())throw new Error("Playwright output directory must be a non-symbolic-link directory");const o=await k.realpath(r);if(!D(t,o))throw new Error("Playwright output directory escaped the artifact root");const h=await Qt(o,a,n),l=[],p=w.join(e,"output");h.candidates.length>0&&await k.mkdir(p,{mode:448,recursive:!1});for(const[f,i]of h.candidates.entries()){const c=await Z(i.file,Y,"Playwright output file",n,i.identity,p,`${f.toString().padStart(5,"0")}-${pt(w.basename(i.file))}${ie(i.file)}`);l.push(I(c.filePath,c.identity,re(i.file),"engine.playwright-output",i.relativePath,"Playwright output"))}return{artifacts:l,bytes:h.bytes,entries:h.entries}}catch(s){if(gt(s))return{artifacts:[],bytes:0,entries:0};throw s}}async function Qt(t,e,r){const a=[];let n=0,s=0;const o=[{depth:0,directory:t}];for(;o.length>0;){const h=o.pop();if(h===void 0)continue;if(h.depth>at)throw new Error(`Playwright output exceeds the ${at.toString()} level directory depth limit`);const l=h.directory,p=await k.readdir(l,{withFileTypes:!0});p.sort((f,i)=>f.name.localeCompare(i.name));for(const f of p){if(s+=1,s>st)throw new X({bytes:n,entries:s,kind:"entries",limit:st,observed:s});const i=w.join(l,f.name),c=await r.lstat(i);if(c.isSymbolicLink())throw new Error("Playwright output must not contain symbolic links");if(c.isDirectory()){o.push({depth:h.depth+1,directory:i});continue}if(!c.isFile())continue;if(c.size<0n||c.size>BigInt(Number.MAX_SAFE_INTEGER))throw new Error("Playwright output file has an invalid size");const m=Number(c.size);if(n+=m,!Number.isSafeInteger(n)||n>Y)throw new X({bytes:n,entries:s,kind:"bytes",limit:Y,observed:n});e.has(i)||a.push({file:i,identity:c,relativePath:U(t,i)})}}return{bytes:n,candidates:a,entries:s}}function Zt(t){return t.kind==="bytes"?`Playwright output exceeds its ${t.limit.toString()} byte aggregate limit (observed ${t.observed.toString()} bytes)`:`Playwright output exceeds its ${t.limit.toString()} entry limit (observed ${t.observed.toString()} entries)`}async function ut(t,e,r,a){if(t===void 0)throw new Error("Playwright path attachment did not declare a path");const n=w.resolve(e,t),s=await a.lstat(n);if(!s.isFile()||s.isSymbolicLink())throw new Error("Playwright attachment path must be a regular non-symbolic-link file");const o=await k.realpath(n);if(!D(e,o)&&!D(r,o))throw new Error("Playwright attachment path must stay within the consumer or artifact root");return{file:o,identity:s}}function qt(t){const e=b(t,"Playwright report.stats");return{expected:F(e.expected,"Playwright stats.expected"),unexpected:F(e.unexpected,"Playwright stats.unexpected"),flaky:F(e.flaky,"Playwright stats.flaky"),skipped:F(e.skipped,"Playwright stats.skipped"),duration:q(e.duration,"Playwright stats.duration")}}function te(t){let e=Nt;const r=[];for(const a of t){if(e<=0)break;const s=Buffer.from(a.text,"utf8").subarray(0,e).toString("utf8");s.length>0&&r.push({...a,text:s}),e-=Buffer.byteLength(s)}return e<=0&&r.push({stream:"engine",text:"Playwright report log evidence was truncated at its bounded limit"}),r}function ee(t){if(t.path!==void 0){const e=w.extname(t.path);if(/^\.[a-z0-9]{1,12}$/iu.test(e))return e}return t.contentType==="application/json"?".json":t.contentType.startsWith("text/")?".txt":t.contentType==="image/png"?".png":t.contentType==="image/jpeg"?".jpg":".bin"}function re(t){const e=w.extname(t).toLowerCase();return e===".json"?"application/json":e===".zip"?"application/zip":e===".png"?"image/png":e===".jpg"||e===".jpeg"?"image/jpeg":e===".webm"?"video/webm":[".log",".txt",".md"].includes(e)?"text/plain; charset=utf-8":"application/octet-stream"}function ie(t){const e=w.extname(t).toLowerCase();return/^\.[a-z0-9]{1,12}$/u.test(e)?e:".bin"}function pt(t){const e=t.trim().toLowerCase().replace(/[^a-z0-9._-]+/gu,"-").replace(/^-+|-+$/gu,"").slice(0,80);return e.length>0?e:"attachment"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{_android as
|
|
1
|
+
import*as e from"@playwright/test";import{_android as t,_baseTest as r,_electron as o,chromium as a,defineConfig as s,devices as i,errors as m,expect as n,firefox as d,mergeExpects as l,mergeTests as f,request as p,selectors as x,test as c,webkit as w}from"@playwright/test";const{_utilityTest:g}=e;export{t as _android,r as _baseTest,o as _electron,g as _utilityTest,a as chromium,s as defineConfig,i as devices,m as errors,n as expect,d as firefox,l as mergeExpects,f as mergeTests,p as request,x as selectors,c as test,w as webkit};export{readPlaywrightContext}from"./runtime-input.js";export{defineTestkitConfig}from"./config.js";export{emulateCpu,emulateNetwork}from"./emulation.js";export{browserRequests}from"./browser-traffic.js";export{assertSafeUiTarget,assertSafeUiTargets,createAuthedApiClient,createSandboxId,createUiSandbox,resolveBackendBaseUrl,resolveFrontendBaseUrl,waitForAnimationFrames,waitForElementStable,waitForImagesLoaded,waitForNetworkIdle,waitForUiSettled}from"./sandbox.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{expect as
|
|
1
|
+
import{expect as E,request as R,test as x}from"@playwright/test";import{readPlaywrightContext as T}from"./runtime-input.js";const u=String;export function createUiSandbox(e={}){const t=createSandboxId(e);return{expect:E,test:x.extend({testkitSandbox:async({page:r},n)=>{await n({id:t,backendBaseUrl:resolveBackendBaseUrl(e),frontendBaseUrl:resolveFrontendBaseUrl(e),page:r})}})}}export function createSandboxId(e={}){const t=u(e.prefix||"ui").replace(/[^A-Za-z0-9_-]+/gu,"-").replace(/^-+|-+$/gu,"")||"ui",r=process.env.TEST_WORKER_INDEX||process.env.TESTKIT_WORKER_INDEX||"0",n=process.env.TESTKIT_LEASE_ID||process.env.TESTKIT_RUNTIME_ID||process.pid;return`${t}-${String(n)}-${r}`}export function resolveFrontendBaseUrl(e={}){const t=f();return m(U(e.frontendBaseUrl,e.baseUrl,l(t,"frontendBaseUrl"),process.env.TESTKIT_FRONTEND_BASE_URL,process.env.BASE_URL),{allowRemote:e.allowRemote??l(t,"allowRemote")})}export function resolveBackendBaseUrl(e={}){const t=f();return m(U(e.backendBaseUrl,l(t,"backendBaseUrl"),process.env.TESTKIT_BACKEND_BASE_URL,process.env.API_BASE_URL),{allowRemote:e.allowRemote??l(t,"allowRemote")})}export function assertSafeUiTarget(e,t={}){const r=p(e);if(!r)throw new Error(`Invalid UI target URL: ${u(e)}`);if(b(t))return r.toString().replace(/\/$/u,"");if(!I(r.hostname))throw new Error(`UI target must be local unless allowRemote is true: ${u(e)}`);return r.toString().replace(/\/$/u,"")}export function assertSafeUiTargets(e=[],t={}){return e.map(r=>assertSafeUiTarget(r,t))}export async function createAuthedApiClient(e=R,t={}){const r=assertSafeUiTarget(resolveBackendBaseUrl(t),t);return e.newContext({baseURL:r,extraHTTPHeaders:t.headers||{}})}export async function waitForUiSettled(e,t={}){const r=t.timeout??5e3;await e.waitForLoadState("domcontentloaded",{timeout:r}),await waitForAnimationFrames(e,t.frames??2),await waitForNetworkIdle(e,{frames:t.frames??2,timeout:r})}export async function waitForAnimationFrames(e,t=2){const r=Math.max(1,Number(t)||1);await e.evaluate(n=>new Promise(i=>{let o=n;const a=()=>{if(o-=1,o<=0){i();return}requestAnimationFrame(a)};requestAnimationFrame(a)}),r)}export async function waitForNetworkIdle(e,t={}){const r=t.timeout??2e3;try{await e.waitForLoadState("networkidle",{timeout:r})}catch{await e.waitForLoadState("load",{timeout:r}).catch(()=>{}),await waitForAnimationFrames(e,t.frames??2)}}export async function waitForElementStable(e,t,r={}){const n=Math.max(1,Number(r.frames)||2),i=e.locator(t);await i.waitFor({state:"visible",timeout:r.timeout}),await i.evaluate(async(o,a)=>{let s=0,c=JSON.stringify(o.getBoundingClientRect().toJSON());for(;s<a;){await new Promise(S=>requestAnimationFrame(()=>{S()}));const d=JSON.stringify(o.getBoundingClientRect().toJSON());d===c?s+=1:s=0,c=d}},n)}export async function waitForImagesLoaded(e,t=null){const r=t?`${t} img`:"img",n=e.locator(r),i=await n.count();for(let o=0;o<i;o+=1)await n.nth(o).evaluate(a=>{if(!(!(a instanceof HTMLImageElement)||a.complete))return new Promise(s=>{const c=()=>{s()};a.addEventListener("load",c,{once:!0}),a.addEventListener("error",c,{once:!0})})})}function m(e,t={}){const r=u(e||"").trim();if(!r)throw new Error("A UI base URL is required");return assertSafeUiTarget(r,t)}function f(){const e=T()?.uiConfig;return g(e)?e:{}}function p(e){try{return new URL(u(e))}catch{return null}}function b(e={}){if(w(e.allowRemote))return!0;const t=f();return w(l(t,"allowRemote"))}function w(e){return e===!0||e==="true"}function I(e){return e==="localhost"||e==="127.0.0.1"||e==="::1"||e.endsWith(".localhost")}function l(e,t){return g(e)?Reflect.get(e,t):void 0}function U(...e){return e.find(Boolean)}function g(e){return typeof e=="object"&&e!==null}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import c from"node:fs/promises";import P from"node:os";import
|
|
1
|
+
import c from"node:fs/promises";import P from"node:os";import n from"node:path";import{toError as w}from"../kernel/error-serialization.js";import{isUnknownRecord as T}from"../kernel/unknown-values.js";import{captureProcessCreationIdentity as N,observeProcessCreationIdentity as O}from"./process-identity.js";const y="testkit-engine-",v=".testkit-owner.json",E=new Set;export async function createInvocationProcessEnvironment(t,r=process.env,e=process.platform){return h(t,n.join(t,"home"),n.join(t,"tmp"),!0,r,e)}export async function createNativeInvocationProcessEnvironment(t,r={}){const e=r.platform??process.platform,s=r.ambientEnvironment??process.env,o=await j(r.nativeTemporaryRootDirectory??I(s,e)),u=await R(e);E.has(o)||(E.add(o),await g(o,e,u));let i=null;try{const a=await c.mkdtemp(n.join(o,y));return i=a,e!=="win32"&&await c.chmod(a,448),i=await b(a,o,e),await c.writeFile(n.join(i,v),JSON.stringify({pid:process.pid,pidNamespace:u,processIdentity:N(process.pid)}),{flag:"wx",mode:384}),await h(t,n.join(i,"home"),i,!1,s,e)}catch(a){if(i===null)throw a;try{await c.rm(i,{force:!0,recursive:!0})}catch(l){throw new AggregateError([w(a),w(l)],`Could not initialize or remove native invocation temporary directory ${i}`,{cause:l})}throw a}}async function h(t,r,e,s,o,u){if(!n.isAbsolute(t))throw new Error("Engine invocation directory must be absolute");if(!n.isAbsolute(e))throw new Error("Engine invocation temporary directory must be absolute");const i=n.join(t,"cache"),a=n.join(t,"config"),l=n.join(t,"data"),f=n.join(t,"local-app-data"),m=n.join(t,"app-data");await Promise.all([r,...s?[e]:[],i,a,l,f,m].map(A=>c.mkdir(A,{mode:448})));const p={APPDATA:m,HOME:r,LOCALAPPDATA:f,TEMP:e,TMP:e,TMPDIR:e,USERPROFILE:r,XDG_CACHE_HOME:i,XDG_CONFIG_HOME:a,XDG_DATA_HOME:l};return d(p,o,"LANG"),d(p,o,"LC_ALL"),d(p,o,"TZ"),u==="win32"&&(p.PATH="",d(p,o,"ComSpec"),d(p,o,"SystemRoot")),Object.freeze({environment:Object.freeze(p),homeDirectory:r,temporaryDirectory:e})}async function R(t){if(t!=="linux")return null;try{return await c.readlink("/proc/self/ns/pid")}catch{return null}}async function g(t,r,e){if(r==="linux"&&e===null)return;let s;try{s=await c.readdir(t)}catch{return}for(const o of s.filter(u=>u.startsWith(y))){const u=n.join(t,o);try{const i=await c.lstat(u);if(!i.isDirectory()||i.isSymbolicLink()||r!=="win32"&&(i.uid!==process.getuid?.()||(i.mode&63)!==0))continue;const a=JSON.parse(await c.readFile(n.join(u,v),"utf8"));if(!T(a)||a.pidNamespace!==e||O(a.pid,a.processIdentity)!=="different")continue;await c.rm(u,{force:!0,recursive:!0})}catch{continue}}}async function j(t){if(!n.isAbsolute(t))throw new Error("Native invocation temporary root must be absolute");const r=await c.realpath(t);if(!(await c.stat(r)).isDirectory())throw new Error("Native invocation temporary root must be a directory");return r}async function b(t,r,e){const s=await c.lstat(t);if(!s.isDirectory()||s.isSymbolicLink())throw new Error("Native invocation temporary directory must be a non-symbolic-link directory");if(e!=="win32"&&(s.mode&63)!==0)throw new Error("Native invocation temporary directory must not grant group or other access");const o=await c.realpath(t);if(n.dirname(o)!==r)throw new Error("Native invocation temporary directory escaped its operating-system root");return o}function I(t,r){if(r!=="win32")return"/tmp";const e=t.TEMP??t.TMP;return e===void 0||e.length===0?P.tmpdir():e}function d(t,r,e){const s=r[e];s!==void 0&&(t[e]=s)}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"entrypoint": "dist/process/process.js",
|
|
4
4
|
"sourceSha256": "e7301ca010796bb92d983c039db47f36e155c26ccfb42ff2d057d47addab3121",
|
|
5
5
|
"bundleSha256": "db5da25a23e4577451c0c047360de70af8abbd63be686dd7b97d65486cbba3b8",
|
|
6
|
-
"packageLockSha256": "
|
|
6
|
+
"packageLockSha256": "ba434d693cbd53c0160909d2aa6c445a3e4649130c953a1f77244465d969a05c",
|
|
7
7
|
"esbuildVersion": "0.28.1",
|
|
8
8
|
"options": {
|
|
9
9
|
"format": "esm",
|
package/dist/repo/history.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{errorMessage as
|
|
2
|
-
`)){if(
|
|
3
|
-
`).map(r=>r.trim()).filter(r=>r.length>0&&!r.endsWith("/HEAD"))}export async function findRemoteBranchHolding(t,
|
|
4
|
-
`).map(a=>a.trim()).filter(a=>a.length>0).sort((a,
|
|
5
|
-
`).flatMap(
|
|
1
|
+
import{errorMessage as $}from"../kernel/error-serialization.js";import{requireManagedHostCommandSuccess as d}from"../process/index.js";import{REPOSITORY_COMMAND_GUARD_ARGS as p,runManagedGitCommand as l}from"./managed-git.js";const x=/^([0-9a-f]{40}|[0-9a-f]{64}) \d+ \d+(?: \d+)?$/u,A=/^committer-time (\d+)$/u,C=/^([0-9a-f]{40}|[0-9a-f]{64})\trefs\/heads\/(.+)$/u,y=50;export async function findIntroducingCommit(t,e,s,n){const r=`${String(n[0])},${String(n[1])}`,i=await l(t,["blame","-w","-M","--porcelain","-L",r,e,"--",s]);if(i.exitCode!==0)throw new Error(`Git cannot blame lines ${String(n[0])} to ${String(n[1])} of ${s} at ${e}: ${H(i.stderr)}`);const f=newestBlamedCommit(parseBlame(i.stdout));if(f===null)throw new Error(`Git blamed no lines of ${s} at ${e}`);return f.boundary&&await M(t)?{status:"outside-history"}:{commit:f.commit,status:"found"}}export function parseBlame(t){const e=new Map;let s=null;for(const n of t.split(`
|
|
2
|
+
`)){if(n.startsWith(" "))continue;const r=x.exec(n);if(r!==null){s=e.get(r[1])??{boundary:!1,committedAt:0},e.set(r[1],s);continue}if(s===null)continue;const i=A.exec(n);i!==null?s.committedAt=Number(i[1]):n==="boundary"&&(s.boundary=!0)}return[...e].map(([n,r])=>({commit:n,...r}))}export function newestBlamedCommit(t){let e=null;for(const s of t)(e===null||s.committedAt>e.committedAt)&&(e=s);return e}export async function listRemoteRefsContaining(t,e){return d("git for-each-ref",await l(t,["for-each-ref","--format=%(refname)","--contains",e,"refs/remotes"])).stdout.split(`
|
|
3
|
+
`).map(r=>r.trim()).filter(r=>r.length>0&&!r.endsWith("/HEAD"))}export async function findRemoteBranchHolding(t,e,s=process.env){const n=await listRemoteRefsContaining(t,e);if(n.length===0)return{status:"missing"};const r=d("git remote",await l(t,["remote"])).stdout.split(`
|
|
4
|
+
`).map(a=>a.trim()).filter(a=>a.length>0).sort((a,c)=>c.length-a.length),i=new Map;for(const a of n){const c=a.slice(13),o=r.find(m=>c.startsWith(`${m}/`));o!==void 0&&i.set(o,(i.get(o)??new Set).add(c.slice(o.length+1)))}const f={env:{...s,GIT_TERMINAL_PROMPT:"0"},shallow:await M(t)},u=[];for(const[a,c]of i){const o=await T(t,a,c,e,f);if(o.status==="found")return o;o.status==="unknown"&&u.push(o)}return u.length===0?{status:"missing"}:{problems:u.flatMap(a=>a.problems),shallow:u.some(a=>a.shallow),status:"unknown"}}async function T(t,e,s,n,r){const i=await b(t,["ls-remote","--heads",e],r.env);if(typeof i=="string")return{problems:[`Git could not list the branches on ${e}: ${i}`],shallow:!1,status:"unknown"};const f=_(i.stdout);let u=!1;for(const a of[f.filter(([c])=>s.has(c)),f.filter(([c])=>!s.has(c))]){const c=[];for(const[,o]of a){const m=await E(t,o,n);if(m==="yes")return{status:"found"};m==="absent"?c.push(o):u=!0}for(let o=0;o<c.length;o+=y){const m=c.slice(o,o+y),h=await b(t,["fetch","--quiet","--no-tags","--no-write-fetch-head",e,...m],r.env);if(typeof h=="string")return{problems:[`Git could not fetch the branch tips from ${e}: ${h}`],shallow:r.shallow&&u,status:"unknown"};for(const g of m){const w=await E(t,g,n);if(w==="yes")return{status:"found"};u||=w==="no"}}}return r.shallow&&u?{problems:[],shallow:!0,status:"unknown"}:{status:"missing"}}async function b(t,e,s){try{const n=await l(t,e,{env:s});return n.exitCode===0?n:S(n.stderr)}catch(n){return $(n)}}function _(t){return t.split(`
|
|
5
|
+
`).flatMap(e=>{const s=C.exec(e.trim());return s===null?[]:[[s[2],s[1]]]})}async function E(t,e,s){const n=await l(t,["merge-base","--is-ancestor",s,e]);return n.exitCode===0?"yes":n.exitCode===1?"no":"absent"}export async function isAncestor(t,e,s){const n=[...p,"merge-base","--is-ancestor",e,s];return(await l(t,n)).exitCode===0}export async function listUncommittedTrackedFiles(t){const e=[...p,"diff","--name-only","-z","HEAD","--"];return d("git diff",await l(t,e)).stdout.split("\0").filter(n=>n.length>0)}export async function listFilesChangedSince(t,e){if((await l(t,["merge-base","--is-ancestor",e,"HEAD"])).exitCode!==0)return null;const n=["diff","--name-only","--diff-filter=d","-z",`${e}..HEAD`,"--"];return d("git diff",await l(t,n)).stdout.split("\0").filter(i=>i.length>0)}async function M(t){return d("git rev-parse",await l(t,["rev-parse","--is-shallow-repository"])).stdout.trim()==="true"}function H(t){return t.trim().split(`
|
|
6
6
|
`).at(-1)??""}function S(t){return t.trim().split(`
|
|
7
7
|
`)[0]??""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const TESTKIT_SUPPORTED_HOST_NODE_RANGE=">=22.15.0 <23.0.0 || >=24.14.1 <25.0.0",TESTKIT_SUPPORTED_HOST_NPM_RANGE=">=11.6.0 <12.0.0";const o=new Map([[22,Object.freeze([22,15,0])],[24,Object.freeze([24,14,1])]]);export function hostNodeIsSupported(r){const n=s(r);if(n===null)return!1;const e=o.get(n[0]);return e===void 0?!1:u(n,e)>=0}function s(r){const n=String(r).split(".");if(n.length!==3)return null;const e=n.map(t=>Number(t));return e.every(t=>Number.isSafeInteger(t)&&t>=0)?[e[0]??0,e[1]??0,e[2]??0]:null}function u(r,n){for(const e of[0,1,2]){if(r[e]>n[e])return 1;if(r[e]<n[e])return-1}return 0}
|
|
1
|
+
export const TESTKIT_SUPPORTED_HOST_NODE_RANGE=">=22.15.0 <23.0.0 || >=24.14.1 <25.0.0 || >=26.13.0 <27.0.0",TESTKIT_SUPPORTED_HOST_NPM_RANGE=">=11.6.0 <12.0.0";const o=new Map([[22,Object.freeze([22,15,0])],[24,Object.freeze([24,14,1])],[26,Object.freeze([26,13,0])]]);export function hostNodeIsSupported(r){const n=s(r);if(n===null)return!1;const e=o.get(n[0]);return e===void 0?!1:u(n,e)>=0}function s(r){const n=String(r).split(".");if(n.length!==3)return null;const e=n.map(t=>Number(t));return e.every(t=>Number.isSafeInteger(t)&&t>=0)?[e[0]??0,e[1]??0,e[2]??0]:null}function u(r,n){for(const e of[0,1,2]){if(r[e]>n[e])return 1;if(r[e]<n[e])return-1}return 0}
|
|
@@ -97,16 +97,10 @@ export interface UiStableLocator {
|
|
|
97
97
|
export interface UiStableElementPage {
|
|
98
98
|
locator(selector: string): UiStableLocator;
|
|
99
99
|
}
|
|
100
|
-
export interface ImageElement {
|
|
101
|
-
readonly complete: boolean;
|
|
102
|
-
addEventListener(type: "error" | "load", listener: () => void, options: {
|
|
103
|
-
once: true;
|
|
104
|
-
}): void;
|
|
105
|
-
}
|
|
106
100
|
export interface UiImageLocator {
|
|
107
101
|
count(): Promise<number>;
|
|
108
102
|
nth(index: number): {
|
|
109
|
-
evaluate(pageFunction: (element:
|
|
103
|
+
evaluate(pageFunction: (element: HTMLElement | SVGElement) => Promise<void> | undefined): Promise<void>;
|
|
110
104
|
};
|
|
111
105
|
}
|
|
112
106
|
export interface UiImagesPage {
|
|
@@ -139,6 +133,7 @@ export declare function waitForElementStable(page: UiStableElementPage, selector
|
|
|
139
133
|
timeout?: number;
|
|
140
134
|
}): Promise<void>;
|
|
141
135
|
export declare function waitForImagesLoaded(page: UiImagesPage, containerSelector?: string | null): Promise<void>;
|
|
136
|
+
export declare const _utilityTest: unknown;
|
|
142
137
|
export type * from "@playwright/test";
|
|
143
138
|
|
|
144
139
|
export {
|
|
@@ -386,6 +386,8 @@ export type VitestSelection = Readonly<{
|
|
|
386
386
|
host: Readonly<Record<string, string>>;
|
|
387
387
|
project: null | string;
|
|
388
388
|
runner: "auto" | "managed" | "repo";
|
|
389
|
+
shuffle: null | number;
|
|
390
|
+
retry?: number;
|
|
389
391
|
}>;
|
|
390
392
|
export interface DiscoverySummary {
|
|
391
393
|
activeFiles: number;
|
package/docs/bug-records.md
CHANGED
|
@@ -85,10 +85,14 @@ Fault: `src/checkout/refunds.ts`, lines 41 to 58.
|
|
|
85
85
|
|
|
86
86
|
The reproduce command runs from the repository root. When `testkit.config.ts` is in a subdirectory, the command carries `--dir <directory>` and the `--file` path is relative to that directory. The `Test:` and `Fault:` lines always give paths from the repository root.
|
|
87
87
|
|
|
88
|
+
When the bug was recorded with `--vitest-shuffle <seed>`, `--timezone <zone>`, or `--retries <n>`, the command carries the same flag, after any Vitest selectors, so it runs the tests in the order, the time zone, or with the retries that make them fail. The facts block does not change.
|
|
89
|
+
|
|
88
90
|
When `testkit bug file` ran the test against [supplied values](../README.md#run-against-a-supplied-environment), the command ends with the `--base-url`, `--endpoint`, `--env-from`, and `--environment-file` flags it was given, as given. They hold no raw secret: `--env-from` names host variables, the environment file stays outside the issue, and `testkit bug file` accepts a URL only without credentials, a query, or a fragment, and an endpoint name only of letters, digits, dot, underscore, and hyphen. It refuses `--database` and `--env`, whose values the command would otherwise carry, and a relative `--environment-file` unless it runs from the repository root. It checks these values, and that every `--env-from` host variable is set, before it contacts the tracker. The command is written for a POSIX shell: flags and simple mappings appear unquoted, and any argument that needs quoting, such as a URL with brackets or a path with a space, is quoted for that shell. The facts block does not change.
|
|
89
91
|
|
|
90
92
|
A fault on one line reads `line 41`.
|
|
91
93
|
|
|
94
|
+
When `testkit bug bisect` found where the test starts to fail, the `Fault:` line is followed by ``The test passes at `<good>` and fails from its child `<bad>`.``, or, for a range, ``The test passes at `<good>` and fails at `<bad>`. The fault came in between them.``, with full commit ids. A line ``Testkit ran it at: `<commit>` fails, `<commit>` passes, ….`` follows, with 12-character commit ids and `gave no verdict` for a skipped commit, and the facts block carries `bisect`. Testkit leaves all three out when the test, its title, or its flags changed since the bisect, or when `bisect.bad` isn't an ancestor of `foundAt`.
|
|
95
|
+
|
|
92
96
|
Testkit redacts the secrets it recognises in the title, in every section, and in `test.title`. It does so when it records the bug and again when it files the issue. `testkit run` applies the same rules when it stores a run result. Testkit recognises these forms.
|
|
93
97
|
|
|
94
98
|
- The value of a `KEY=value` assignment whose key names a secret.
|
|
@@ -142,29 +146,34 @@ A body with no start marker has no facts. A body with a start marker and no read
|
|
|
142
146
|
|
|
143
147
|
All paths in the block use `/` and are relative to the repository root.
|
|
144
148
|
|
|
145
|
-
| Field | Type | Written | Meaning
|
|
146
|
-
| ------------------- | -------------------------------------------------------- | -------------------------------------- |
|
|
147
|
-
| `v` | `1` | Always | The format version.
|
|
148
|
-
| `cause` | Cause id | Always | The kind of mistake in the code.
|
|
149
|
-
| `impact` | Impact id | Always | What is at stake for the business.
|
|
150
|
-
| `confidence.cause` | `high` or `low` | Always | The finder's confidence in `cause`.
|
|
151
|
-
| `confidence.impact` | `high` or `low` | Always | The finder's confidence in `impact`.
|
|
152
|
-
| `escape` | Escape id | Always | Why the existing tests did not catch the bug.
|
|
153
|
-
| `caughtBy` | `campaign` or `manual` | Always | `campaign` when a campaign was active at record time.
|
|
154
|
-
| `campaign` | Campaign id | With `caughtBy: "campaign"` | The campaign that caught the bug.
|
|
155
|
-
| `testType` | `ui`, `e2e`, `int`, `contract`, `data`, `dal`, or `load` | When the test file's name gives a type | The type of the regression test, from its file suffix.
|
|
156
|
-
| `test.file` | Path | Always | The regression test file.
|
|
157
|
-
| `test.title` | String | When the issue covers one test | The failure's title. See [Known failures](#known-failures).
|
|
158
|
-
| `foundAt` | Full commit id | Always | The commit the test failed at.
|
|
159
|
-
| `fault.path` | Path | Always | The source file that holds the mistake.
|
|
160
|
-
| `fault.lines` | `[start, end]` | Always | The first and last faulty line at `foundAt`, counted from 1.
|
|
161
|
-
| `area.id` | Area id | When the area map claims `fault.path` | The area's key in `areas`.
|
|
162
|
-
| `area.name` | String | With `area.id` | The area's name at filing time.
|
|
163
|
-
| `area.share` | Number from 0 to 1 | With `area.id` | The area's share of the repository's source bytes at `foundAt`.
|
|
164
|
-
| `introducedBy` | Full commit id | When the blame resolves | The newest commit among the fault lines at `foundAt`.
|
|
165
|
-
| `introducedMethod` | `blame` | With `introducedBy` | How Testkit found `introducedBy`.
|
|
166
|
-
|
|
167
|
-
|
|
149
|
+
| Field | Type | Written | Meaning |
|
|
150
|
+
| ------------------- | -------------------------------------------------------- | -------------------------------------- | ------------------------------------------------------------------- |
|
|
151
|
+
| `v` | `1` | Always | The format version. |
|
|
152
|
+
| `cause` | Cause id | Always | The kind of mistake in the code. |
|
|
153
|
+
| `impact` | Impact id | Always | What is at stake for the business. |
|
|
154
|
+
| `confidence.cause` | `high` or `low` | Always | The finder's confidence in `cause`. |
|
|
155
|
+
| `confidence.impact` | `high` or `low` | Always | The finder's confidence in `impact`. |
|
|
156
|
+
| `escape` | Escape id | Always | Why the existing tests did not catch the bug. |
|
|
157
|
+
| `caughtBy` | `campaign` or `manual` | Always | `campaign` when a campaign was active at record time. |
|
|
158
|
+
| `campaign` | Campaign id | With `caughtBy: "campaign"` | The campaign that caught the bug. |
|
|
159
|
+
| `testType` | `ui`, `e2e`, `int`, `contract`, `data`, `dal`, or `load` | When the test file's name gives a type | The type of the regression test, from its file suffix. |
|
|
160
|
+
| `test.file` | Path | Always | The regression test file. |
|
|
161
|
+
| `test.title` | String | When the issue covers one test | The failure's title. See [Known failures](#known-failures). |
|
|
162
|
+
| `foundAt` | Full commit id | Always | The commit the test failed at. |
|
|
163
|
+
| `fault.path` | Path | Always | The source file that holds the mistake. |
|
|
164
|
+
| `fault.lines` | `[start, end]` | Always | The first and last faulty line at `foundAt`, counted from 1. |
|
|
165
|
+
| `area.id` | Area id | When the area map claims `fault.path` | The area's key in `areas`. |
|
|
166
|
+
| `area.name` | String | With `area.id` | The area's name at filing time. |
|
|
167
|
+
| `area.share` | Number from 0 to 1 | With `area.id` | The area's share of the repository's source bytes at `foundAt`. |
|
|
168
|
+
| `introducedBy` | Full commit id | When the blame resolves | The newest commit among the fault lines at `foundAt`. |
|
|
169
|
+
| `introducedMethod` | `blame` | With `introducedBy` | How Testkit found `introducedBy`. |
|
|
170
|
+
| `bisect.good` | Full commit id | After `testkit bug bisect` finds it | The newest commit, on the first-parent line, where the test passes. |
|
|
171
|
+
| `bisect.bad` | Full commit id | With `bisect.good` | The oldest commit after `bisect.good` where the test fails. |
|
|
172
|
+
| `bisect.exact` | `true` or `false` | With `bisect.good` | Whether `bisect.good` is the first parent of `bisect.bad`. |
|
|
173
|
+
|
|
174
|
+
`testkit bug bisect` runs the regression test at commits on the first-parent line of `foundAt`. When `bisect.exact` is `true`, `bisect.bad` is the commit that made the test fail. Otherwise Testkit skipped the commits between them or ran out of probes, and the fault came in among them. `introducedBy` still says what `git blame` found, so the two can disagree, for example when a later commit only reformatted the faulty line.
|
|
175
|
+
|
|
176
|
+
Testkit writes every field it can resolve. A reader accepts a version 1 block that lacks `escape`, `campaign`, `testType`, `test.title`, `fault`, `fault.lines`, `area`, `introducedBy`, `introducedMethod`, or `bisect`, because issues that predate the CLI were given facts by hand and a test file's name may give no type. A reader ignores fields it does not know.
|
|
168
177
|
|
|
169
178
|
A reader accepts every `testType` in the list above, including a type that Testkit no longer writes. Issues keep the type they were filed with, so the list never loses a value. A new type is a format change.
|
|
170
179
|
|
package/docs/engines.md
CHANGED
|
@@ -271,7 +271,7 @@ timeouts, and process failures keep their precedence over both.
|
|
|
271
271
|
## Installation
|
|
272
272
|
|
|
273
273
|
Consumers declare one dependency, `@gfdelarue/testkit`. It installs Vitest
|
|
274
|
-
4.1.11 and Playwright 1.
|
|
274
|
+
4.1.11 and Playwright 1.64.1. Plain Vitest test files run with the product's own
|
|
275
275
|
Vitest 4.x and its config; see
|
|
276
276
|
[Plain Vitest test files](../README.md#plain-vitest-test-files). Otherwise a
|
|
277
277
|
product's copies of Vitest, Vite, esbuild, TypeScript, or Playwright don't
|