@gfdelarue/testkit 0.6.3 → 0.6.4

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.
@@ -1 +1 @@
1
- import{isEngineTaskDeadlineExceededReason as d}from"../api/index.js";import"../../domain/test-outcomes.js";import{toError as l}from"../../kernel/error-serialization.js";import{redactedPrefix as u}from"../../kernel/redaction.js";export async function executeEngineTasks(e,t,n,i,r){const a=[];for(const o of t){e.observer?.taskStarted(o.task);const s=Date.now(),c=taskBeforeStartOutcome(o.task,e.signal,s,n);c!==null?a.push(c):o.preparationFailure!==null?a.push(terminalOutcome(o.task.id,"failed",s,emptyEvidence(),o.preparationFailure)):a.push(await i(o,s))}return engineExecutionResult(e,a,r)}export function engineExecutionResult(e,t,n,i=[]){for(const r of t)e.observer?.evidenceRecorded(r.taskId,r.evidence),e.observer?.taskFinished(r);return{evidence:{artifacts:t.flatMap(r=>r.evidence.artifacts),logs:t.flatMap(r=>r.evidence.logs),metrics:t.flatMap(r=>r.evidence.metrics),native:n},invocationId:e.invocationId,outcomes:t,...i.length===0?{}:{runErrors:i}}}export function taskBeforeStartOutcome(e,t,n,i){return e.deadlineMs<=n||d(t.reason)?terminalOutcome(e.id,"failed",n,emptyEvidence(),taskTimeoutFailure(e.timeoutMs,i)):t.aborted?terminalOutcome(e.id,"cancelled",n,emptyEvidence(),{code:`${i.codePrefix}_CANCELLED`,message:`${i.label} task was cancelled before its process started`}):null}export function taskTimeoutFailure(e,t){return{code:`${t.codePrefix}_TASK_TIMEOUT`,details:{timeoutMs:e},message:`${t.label} task exceeded its ${e.toString()}ms timeout`}}export function managedProcessFailure(e,t,n=null){return e.terminationError===null?null:{classification:"infra",code:`${t.codePrefix}_PROCESS_TERMINATION_INCOMPLETE`,details:{cancelled:e.cancelled,...n===null?{}:{evidenceCollectionFailure:errorDetails(n)},outputLimitExceeded:e.outputLimitExceeded,timedOut:e.timedOut},message:`${t.label} managed-process termination was incomplete: ${e.terminationError.message}`,...e.terminationError.stack===void 0?{}:{stack:e.terminationError.stack}}}export function classifyManagedProcessResult(e,t,n,i){const r=o=>({failure:o,status:"failed"}),a=managedProcessFailure(e,i);return a!==null?r(a):e.timedOut||n?r(taskTimeoutFailure(t,i)):e.cancelled?{failure:{code:`${i.codePrefix}_CANCELLED`,message:`${i.label} task was cancelled`},status:"cancelled"}:e.outputLimitExceeded?r({code:`${i.codePrefix}_OUTPUT_LIMIT`,message:`${i.label} task exceeded the bounded stdout/stderr capture limit`}):e.error!==null?r({...failureFromError(e.error,`${i.codePrefix}_PROCESS_START_FAILED`),classification:"infra"}):null}export function processExitDetails(e){return{...e.exitCode===null?{}:{exitCode:e.exitCode},...e.exitSignal===null?{}:{exitSignal:e.exitSignal}}}export function processExitFailure(e,t,n=null,i=t.label){return{failure:{code:`${t.codePrefix}_PROCESS_FAILED`,details:processExitDetails(e),message:n??(e.exitCode===null?`${i} process terminated by ${e.exitSignal??"an unknown signal"}`:`${i} process exited with code ${e.exitCode.toString()}`)},status:"failed"}}export function firstOutputLine(e){const t=e.split(/\r?\n/u).map(n=>n.trim()).find(Boolean);return t===void 0?null:u(t,4e3)}export function terminalOutcome(e,t,n,i,r){const a=Date.now();return{durationMs:Math.max(0,a-n),evidence:i,failure:r,finishedAtMs:a,startedAtMs:n,status:t,taskId:e}}export function processOutputEvidence(e){return{artifacts:[],logs:[...e.stdout.length===0?[]:[{stream:"stdout",text:e.stdout}],...e.stderr.length===0?[]:[{stream:"stderr",text:e.stderr}]],metrics:[]}}export function emptyEvidence(){return{artifacts:[],logs:[],metrics:[]}}export function failureFromError(e,t){const n=l(e);return{code:t,message:n.message,...n.stack===void 0?{}:{stack:n.stack}}}export function errorDetails(e){return{message:e.message,name:e.name,...e.stack===void 0?{}:{stack:e.stack}}}
1
+ import{isEngineTaskDeadlineExceededReason as d}from"../api/index.js";import"../../domain/test-outcomes.js";import{toError as l}from"../../kernel/error-serialization.js";import{redactedPrefix as u}from"../../kernel/redaction.js";export async function executeEngineTasks(e,t,n,i,a){const r=[];for(const o of t){e.observer?.taskStarted(o.task);const s=Date.now(),c=taskBeforeStartOutcome(o.task,e.signal,s,n);c!==null?r.push(c):o.preparationFailure!==null?r.push(terminalOutcome(o.task.id,"failed",s,emptyEvidence(),o.preparationFailure)):r.push(await i(o,s))}return engineExecutionResult(e,r,a)}export function engineExecutionResult(e,t,n,i=[]){for(const a of t)e.observer?.evidenceRecorded(a.taskId,a.evidence),e.observer?.taskFinished(a);return{evidence:{artifacts:t.flatMap(a=>a.evidence.artifacts),logs:t.flatMap(a=>a.evidence.logs),metrics:t.flatMap(a=>a.evidence.metrics),native:n},invocationId:e.invocationId,outcomes:t,...i.length===0?{}:{runErrors:i}}}export function taskBeforeStartOutcome(e,t,n,i){return e.deadlineMs<=n||d(t.reason)?terminalOutcome(e.id,"failed",n,emptyEvidence(),taskTimeoutFailure(e.timeoutMs,i)):t.aborted?terminalOutcome(e.id,"cancelled",n,emptyEvidence(),{code:`${i.codePrefix}_CANCELLED`,message:`${i.label} task was cancelled before its process started`}):null}export function taskTimeoutFailure(e,t){return{code:`${t.codePrefix}_TASK_TIMEOUT`,details:{timeoutMs:e},message:`${t.label} task exceeded its ${e.toString()}ms timeout`}}export function managedProcessFailure(e,t,n=null){return e.terminationError===null?null:{classification:"infra",code:`${t.codePrefix}_PROCESS_TERMINATION_INCOMPLETE`,details:{cancelled:e.cancelled,...n===null?{}:{evidenceCollectionFailure:errorDetails(n)},outputLimitExceeded:e.outputLimitExceeded,timedOut:e.timedOut},message:`${t.label} managed-process termination was incomplete: ${e.terminationError.message}`,...e.terminationError.stack===void 0?{}:{stack:e.terminationError.stack}}}export function classifyManagedProcessResult(e,t,n,i){const a=o=>({failure:o,status:"failed"}),r=managedProcessFailure(e,i);return r!==null?a(r):e.timedOut||n?a(taskTimeoutFailure(t,i)):e.cancelled?{failure:{code:`${i.codePrefix}_CANCELLED`,message:`${i.label} task was cancelled`},status:"cancelled"}:e.outputLimitExceeded?a({code:`${i.codePrefix}_OUTPUT_LIMIT`,message:`${i.label} task exceeded the bounded stdout/stderr capture limit`}):e.error!==null?a({...failureFromError(e.error,`${i.codePrefix}_PROCESS_START_FAILED`),classification:"infra"}):null}export function processExitDetails(e){return{...e.exitCode===null?{}:{exitCode:e.exitCode},...e.exitSignal===null?{}:{exitSignal:e.exitSignal}}}export function processExitFailure(e,t,n=null,i=t.label){return{failure:{code:`${t.codePrefix}_PROCESS_FAILED`,details:processExitDetails(e),message:n??(e.exitCode===null?`${i} process terminated by ${e.exitSignal??"an unknown signal"}`:`${i} process exited with code ${e.exitCode.toString()}`)},status:"failed"}}const f=/^\(node:\d+\) (?:\[\w+\] )?\w*Warning: /u,m=/^\(Use `node --trace-\w+ \.\.\.` /u;export function firstOutputLine(e){const t=e.split(/\r?\n/u).map(n=>n.trim()).find(n=>n!==""&&!f.test(n)&&!m.test(n));return t===void 0?null:u(t,4e3)}export function terminalOutcome(e,t,n,i,a){const r=Date.now();return{durationMs:Math.max(0,r-n),evidence:i,failure:a,finishedAtMs:r,startedAtMs:n,status:t,taskId:e}}export function processOutputEvidence(e){return{artifacts:[],logs:[...e.stdout.length===0?[]:[{stream:"stdout",text:e.stdout}],...e.stderr.length===0?[]:[{stream:"stderr",text:e.stderr}]],metrics:[]}}export function emptyEvidence(){return{artifacts:[],logs:[],metrics:[]}}export function failureFromError(e,t){const n=l(e);return{code:t,message:n.message,...n.stack===void 0?{}:{stack:n.stack}}}export function errorDetails(e){return{message:e.message,name:e.name,...e.stack===void 0?{}:{stack:e.stack}}}
@@ -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")),d=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(d.attachmentDirectory)}catch(w){if(!(w instanceof Error&&"code"in w&&w.code==="ENOTDIR"))throw w}}),p.push({artifactDirectory:f,inputs:I,task:c,preparationFailure:null,prepared:{baseUrl:m,sourceFile:E,workspace:d}})}catch(m){const E=(await yr([_,f],t)).map(d=>d.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?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
+ 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 w}from"../../runtime-input-file.js";import b from"node:crypto";import s from"node:fs/promises";import o from"node:path";import{fileURLToPath as P,pathToFileURL as y}from"node:url";import{createManagedNodeModuleResolution as j}from"../../managed-module-resolution.js";import{materializePlaywrightRuntimeInput as v,playwrightRuntimeInputBootstrapSource as T}from"../runtime/runtime-input.js";import{nativeReporterFile as O}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{F(e.inputs.supportModules),r=await k(e.rootDirectory,e.inputs)}catch(m){throw new PlaywrightConsumerConfigurationError(m)}await s.mkdir(e.taskDirectory,{recursive:!0}),await s.mkdir(e.artifactDirectory,{recursive:!0});const t=b.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`),g=o.join(e.taskDirectory,"testkit-playwright-report.json"),d=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 v({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=O("playwright");return await h(l,playwrightResolutionBootstrapSource(e.managedModuleEntrypoints,{consumerRoots:[e.rootDirectory],trustedImporterFiles:[n,a],trustedInternalEntrypoints:[e.moduleFile,f,P(w)]})),await h(a,T({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:g,reportersFinishedFile:d,workspaceDirectory:e.taskDirectory},rootDirectory:e.rootDirectory,sourceFile:e.sourceFile})),{attachmentDirectory:c,configFile:n,inputBootstrapFile:a,outputDirectory:p,reportFile:g,reportersFinishedFile:d,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 j({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/common/process.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`
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 f}from"node:url";import{createManagedNodeModuleResolution as v}from"../../managed-module-resolution.js";import{materializePlaywrightRuntimeInput as T,playwrightRuntimeInputBootstrapSource as k}from"../runtime/runtime-input.js";import{nativeReporterFile as x}from"../../adapters/native-report.js";import{requireConsumerFile as O}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{F(e.inputs.supportModules),r=await D(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}),y=x("playwright");return await h(l,playwrightResolutionBootstrapSource(e.managedModuleEntrypoints,{consumerRoots:[e.rootDirectory],trustedImporterFiles:[n,a],trustedInternalEntrypoints:[e.moduleFile,y,j(b)]})),await h(a,k({digest:u.digest,file:u.file,sizeBytes:u.sizeBytes,invocationId:e.invocationId,taskId:e.taskId})),await h(n,S({baseConfigFile:r,baseUrl:e.baseUrl,browserFile:e.browserFile,inputBootstrapFile:a,outputDirectory:p,reporterFile:y,report:{attachmentDirectory:c,identity:{engineId:"playwright",invocationId:e.invocationId,taskId:e.taskId,sourceFile:e.sourceFile},reportFile:d,reportersFinishedFile:g,workspaceDirectory:e.taskDirectory},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 S(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:f(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:f(e.inputBootstrapFile).href,outputDirectory:e.outputDirectory,report:e.report,reporterFile:e.reporterFile,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
@@ -167,6 +168,13 @@ export default {
167
168
  }),
168
169
  globalSetup: normalizeSetups(base.globalSetup),
169
170
  globalTeardown: normalizeSetups(base.globalTeardown),
171
+ build: {
172
+ ...base.build,
173
+ external: [
174
+ ...(baseExternal === undefined ? [] : Array.isArray(baseExternal) ? baseExternal : [baseExternal]),
175
+ new RegExp(OPTIONS.testkitPackageMatchSource, "u"),
176
+ ],
177
+ },
170
178
  outputDir: OPTIONS.outputDirectory,
171
179
  preserveOutput: "always",
172
180
  reporter: normalizeReporters(base.reporter),
@@ -174,4 +182,4 @@ export default {
174
182
  fullyParallel: false,
175
183
  workers: 1,
176
184
  };
177
- `}export function playwrightExactFileFilter(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 k(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 F(e){const r=Object.keys(e).filter(t=>t!=="playwrightConfig");if(r.length>0)throw new Error(`Playwright engine does not support support module(s): ${r.join(", ")}`)}async function h(e,r){const t=await s.open(e,"wx",384);try{await t.writeFile(r,"utf8")}finally{await t.close()}}
185
+ `}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 D(e,r){if(!Object.hasOwn(r.supportModules,"playwrightConfig"))return null;const t=r.supportModules.playwrightConfig;if(!o.isAbsolute(t))throw new Error("supportModules.playwrightConfig must be an absolute path");return O(e,t,"supportModules.playwrightConfig")}function F(e){const r=Object.keys(e).filter(t=>t!=="playwrightConfig");if(r.length>0)throw new Error(`Playwright engine does not support support module(s): ${r.join(", ")}`)}async function 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 T 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 b,nonNegativeNumber as q,optionalNonEmptyString as W,optionalNonNegativeInteger as tt,portableRelativePath as U,portableReportPath as et,requireNonEmptyString as x,requireRecord as S}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 T.mkdir(t.artifactDirectory,{recursive:!0});const r=await T.realpath(t.artifactDirectory),a=await T.realpath(t.rootDirectory),n=w.join(r,Mt);await T.mkdir(n,{mode:448,recursive:!1});const s=[],o=[];for(const c of["stdout","stderr"]){const g=t[c];if(g.length===0)continue;s.push({stream:c,text:g});const F=z(Buffer.from(g,"utf8")),_=await H(n,`${c}.log`,F.contents);o.push(I(_,F,"text/plain; charset=utf-8","engine.process-output",`playwright-${c}`,`${F.sizeBytes.toString()} bytes`))}let h=null,u=null,p=new Set,f=[];try{const c=await wt(t.reportFile,Ot,"Testkit native Playwright report",e);if(c.file===null)h=new Error("Playwright did not produce its Testkit native report");else{u=Ut(vt(c.value,t.identity).result,t.sourceFile);const g=u.carriesEnvironmentFailures?z(Buffer.from(Yt(c.value))):c.file,F=await H(n,"testkit-playwright-report.json",g.contents);o.push(I(F,g,"application/json","engine.testkit-playwright-report","testkit-playwright-report","Testkit native Playwright report")),s.push(...u.logs);const _=await Et(n,u.failureDetails);_!==null&&o.push(_);const y=await Kt(r,n,a,U(a,t.sourceFile),u,e),d=await Vt(r,n,a,u.attachments.filter(R=>!lt(R)),e);o.push(...y.artifacts,...d.artifacts),p=new Set([...y.sourcePaths,...d.sourcePaths]),f=[...y.failures,...d.failures]}}catch(c){h=ht(c)}let i={artifacts:[],bytes:0,entries:0},l=null;try{i=await Jt(r,n,t.outputDirectory,p,e),o.push(...i.artifacts)}catch(c){if(!(c instanceof X))throw c;l=c.limit,s.push({stream:"engine",text:c.message})}const m=Ht(u,a,t.sourceFile,f.length,await Wt(t.reportersFinishedFile)),E=l??i;return{attachmentFailures:f,evidence:{artifacts:o,logs:s,metrics:[...Object.entries(u?.stats??{}).map(([c,g])=>({name:c==="duration"?"playwright.duration":`playwright.tests.${c}`,unit:c==="duration"?"milliseconds":"tests",value:g})),...["bytes","entries"].map(c=>({name:`playwright.output.${c}`,unit:c,value:E[c]}))],...u===null?{}:{native:{playwright:{attachmentFailures:f.map(c=>({...c})),specs:u.specs,stats:u.stats}}}},outputLimit:l,report:m,reportError:h}}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)),u=t.failOnFlakyTests?t.stats.flaky:0;if(t.stats.unexpected+u>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+u).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 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 e,_baseTest as r,_electron as t,chromium as o,defineConfig as a,devices as s,errors as i,expect as m,firefox as d,mergeExpects as n,mergeTests as f,request as l,selectors as x,test as p,webkit as c}from"@playwright/test";export{e as _android,r as _baseTest,t as _electron,o as chromium,a as defineConfig,s as devices,i as errors,m as expect,d as firefox,n as mergeExpects,f as mergeTests,l as request,x as selectors,p as test,c 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
+ 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 R,request as x,test as E}from"@playwright/test";import{readPlaywrightContext as p}from"./runtime-input.js";const c=String;export function createUiSandbox(e={}){const t=createSandboxId(e);return{expect:R,test:E.extend({testkitSandbox:async({page:r},n)=>{await n({id:t,backendBaseUrl:resolveBackendBaseUrl(e),frontendBaseUrl:resolveFrontendBaseUrl(e),page:r})}})}}export function createSandboxId(e={}){const t=c(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=l();return m(U(e.frontendBaseUrl,e.baseUrl,u(t,"frontendBaseUrl"),process.env.TESTKIT_FRONTEND_BASE_URL,process.env.BASE_URL),{allowRemote:e.allowRemote??u(t,"allowRemote")})}export function resolveBackendBaseUrl(e={}){const t=l();return m(U(e.backendBaseUrl,u(t,"backendBaseUrl"),process.env.TESTKIT_BACKEND_BASE_URL,process.env.API_BASE_URL),{allowRemote:e.allowRemote??u(t,"allowRemote")})}export function assertSafeUiTarget(e,t={}){const r=T(e);if(!r)throw new Error(`Invalid UI target URL: ${c(e)}`);if(b(t))return r.toString().replace(/\/$/u,"");if(!_(r.hostname))throw new Error(`UI target must be local unless allowRemote is true: ${c(e)}`);return r.toString().replace(/\/$/u,"")}export function assertSafeUiTargets(e=[],t={}){return e.map(r=>assertSafeUiTarget(r,t))}export async function createAuthedApiClient(e=x,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,f=JSON.stringify(o.getBoundingClientRect().toJSON());for(;s<a;){await new Promise(g=>requestAnimationFrame(()=>{g()}));const d=JSON.stringify(o.getBoundingClientRect().toJSON());d===f?s+=1:s=0,f=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.complete)return new Promise(s=>{a.addEventListener("load",s,{once:!0}),a.addEventListener("error",s,{once:!0})})})}function m(e,t={}){const r=c(e||"").trim();if(!r)throw new Error("A UI base URL is required");return assertSafeUiTarget(r,t)}function l(){const e=p()?.uiConfig;return S(e)?e:{}}function T(e){try{return new URL(c(e))}catch{return null}}function b(e={}){if(w(e.allowRemote))return!0;const t=l();return w(u(t,"allowRemote"))}function w(e){return e===!0||e==="true"}function _(e){return e==="localhost"||e==="127.0.0.1"||e==="::1"||e.endsWith(".localhost")}function u(e,t){return S(e)?Reflect.get(e,t):void 0}function U(...e){return e.find(Boolean)}function S(e){return typeof e=="object"&&e!==null}
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 a 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",h=new Set;export async function createInvocationProcessEnvironment(t,r=process.env,e=process.platform){const o=a.join(t,"tmp");return E(t,o,!0,r,e)}export async function createNativeInvocationProcessEnvironment(t,r={}){const e=r.platform??process.platform,o=r.ambientEnvironment??process.env,u=await g(r.nativeTemporaryRootDirectory??I(o,e)),s=await R(e);h.has(u)||(h.add(u),await b(u,e,s));let i=null;try{const n=await c.mkdtemp(a.join(u,y));return i=n,e!=="win32"&&await c.chmod(n,448),i=await j(n,u,e),await c.writeFile(a.join(i,v),JSON.stringify({pid:process.pid,pidNamespace:s,processIdentity:N(process.pid)}),{flag:"wx",mode:384}),await E(t,i,!1,o,e)}catch(n){if(i===null)throw n;try{await c.rm(i,{force:!0,recursive:!0})}catch(m){throw new AggregateError([w(n),w(m)],`Could not initialize or remove native invocation temporary directory ${i}`,{cause:m})}throw n}}async function E(t,r,e,o,u){if(!a.isAbsolute(t))throw new Error("Engine invocation directory must be absolute");if(!a.isAbsolute(r))throw new Error("Engine invocation temporary directory must be absolute");const s=a.join(t,"home"),i=a.join(t,"cache"),n=a.join(t,"config"),m=a.join(t,"data"),d=a.join(t,"local-app-data"),f=a.join(t,"app-data");await Promise.all([s,...e?[r]:[],i,n,m,d,f].map(A=>c.mkdir(A,{mode:448})));const p={APPDATA:f,HOME:s,LOCALAPPDATA:d,TEMP:r,TMP:r,TMPDIR:r,USERPROFILE:s,XDG_CACHE_HOME:i,XDG_CONFIG_HOME:n,XDG_DATA_HOME:m};return l(p,o,"LANG"),l(p,o,"LC_ALL"),u==="win32"&&(p.PATH="",l(p,o,"ComSpec"),l(p,o,"SystemRoot")),Object.freeze({environment:Object.freeze(p),homeDirectory:s,temporaryDirectory:r})}async function R(t){if(t!=="linux")return null;try{return await c.readlink("/proc/self/ns/pid")}catch{return null}}async function b(t,r,e){if(r==="linux"&&e===null)return;let o;try{o=await c.readdir(t)}catch{return}for(const u of o.filter(s=>s.startsWith(y))){const s=a.join(t,u);try{const i=await c.lstat(s);if(!i.isDirectory()||i.isSymbolicLink()||r!=="win32"&&(i.uid!==process.getuid?.()||(i.mode&63)!==0))continue;const n=JSON.parse(await c.readFile(a.join(s,v),"utf8"));if(!T(n)||n.pidNamespace!==e||O(n.pid,n.processIdentity)!=="different")continue;await c.rm(s,{force:!0,recursive:!0})}catch{continue}}}async function g(t){if(!a.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 j(t,r,e){const o=await c.lstat(t);if(!o.isDirectory()||o.isSymbolicLink())throw new Error("Native invocation temporary directory must be a non-symbolic-link directory");if(e!=="win32"&&(o.mode&63)!==0)throw new Error("Native invocation temporary directory must not grant group or other access");const u=await c.realpath(t);if(a.dirname(u)!==r)throw new Error("Native invocation temporary directory escaped its operating-system root");return u}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 l(t,r,e){const o=r[e];o!==void 0&&(t[e]=o)}
1
+ import c from"node:fs/promises";import P from"node:os";import i from"node:path";import{toError as w}from"../kernel/error-serialization.js";import{isUnknownRecord as N}from"../kernel/unknown-values.js";import{captureProcessCreationIdentity as T,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,i.join(t,"home"),i.join(t,"tmp"),!0,r,e)}export async function createNativeInvocationProcessEnvironment(t,r={}){const e=r.platform??process.platform,s=r.ambientEnvironment??process.env,n=await g(r.nativeTemporaryRootDirectory??I(s,e)),u=await R(e);E.has(n)||(E.add(n),await b(n,e,u));let o=null;try{const a=await c.mkdtemp(i.join(n,y));return o=a,e!=="win32"&&await c.chmod(a,448),o=await j(a,n,e),await c.writeFile(i.join(o,v),JSON.stringify({pid:process.pid,pidNamespace:u,processIdentity:T(process.pid)}),{flag:"wx",mode:384}),await h(t,i.join(o,"home"),o,!1,s,e)}catch(a){if(o===null)throw a;try{await c.rm(o,{force:!0,recursive:!0})}catch(l){throw new AggregateError([w(a),w(l)],`Could not initialize or remove native invocation temporary directory ${o}`,{cause:l})}throw a}}async function h(t,r,e,s,n,u){if(!i.isAbsolute(t))throw new Error("Engine invocation directory must be absolute");if(!i.isAbsolute(e))throw new Error("Engine invocation temporary directory must be absolute");const o=i.join(t,"cache"),a=i.join(t,"config"),l=i.join(t,"data"),f=i.join(t,"local-app-data"),m=i.join(t,"app-data");await Promise.all([r,...s?[e]:[],o,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:o,XDG_CONFIG_HOME:a,XDG_DATA_HOME:l};return d(p,n,"LANG"),d(p,n,"LC_ALL"),u==="win32"&&(p.PATH="",d(p,n,"ComSpec"),d(p,n,"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 b(t,r,e){if(r==="linux"&&e===null)return;let s;try{s=await c.readdir(t)}catch{return}for(const n of s.filter(u=>u.startsWith(y))){const u=i.join(t,n);try{const o=await c.lstat(u);if(!o.isDirectory()||o.isSymbolicLink()||r!=="win32"&&(o.uid!==process.getuid?.()||(o.mode&63)!==0))continue;const a=JSON.parse(await c.readFile(i.join(u,v),"utf8"));if(!N(a)||a.pidNamespace!==e||O(a.pid,a.processIdentity)!=="different")continue;await c.rm(u,{force:!0,recursive:!0})}catch{continue}}}async function g(t){if(!i.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 j(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 n=await c.realpath(t);if(i.dirname(n)!==r)throw new Error("Native invocation temporary directory escaped its operating-system root");return n}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": "baeb7a46d3a7c0a092ff04f5a5d82115ff9ed748bb9563fdda2e154aa9ab19e3",
6
+ "packageLockSha256": "41f889dea57e892f03549d17c252c8cd601b637210d1c9fbb5ff095a4f34f506",
7
7
  "esbuildVersion": "0.28.1",
8
8
  "options": {
9
9
  "format": "esm",
@@ -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: ImageElement) => Promise<void> | undefined): Promise<void>;
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 {
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.59.1. Plain Vitest test files run with the product's own
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
@@ -1,3 +1,43 @@
1
+ # 0.6.4
2
+
3
+ ## Testkit supports Node 26 LTS
4
+
5
+ Testkit runs on Node 26 from 26.13.0, the first Node 26 LTS release. Before
6
+ this release, `testkit` exited with `TESTKIT_HOST_NODE_UNSUPPORTED` on any
7
+ Node 26, which Homebrew, Arch, and Scoop now install.
8
+ `engines.node` is now `>=22.15.0 <23.0.0 || >=24.14.1 <25.0.0 || >=26.13.0 <27.0.0`.
9
+ Support for Node 22 and 24 is unchanged.
10
+
11
+ When an engine process exited before it wrote its report, Testkit took the
12
+ failure message from the first line of its output. On Node 26, Playwright
13
+ 1.59.1 printed a `DEP0205` deprecation warning first, so Testkit reported the
14
+ warning instead of the error. Testkit now skips Node's warning lines.
15
+
16
+ ## Testkit runs Playwright 1.64.1
17
+
18
+ Testkit's Playwright moves from 1.59.1 to 1.64.1, and its Chromium from 147 to 156. Run `testkit setup playwright` once after you upgrade. Until you do, a ui
19
+ task stops with `TESTKIT_PLAYWRIGHT_INSTALLATION_INVALID`, because Chromium 156
20
+ isn't installed yet.
21
+
22
+ Playwright 1.59.1 couldn't unpack Chromium on Node 26, so
23
+ `testkit setup playwright` hung there until its 20-minute limit. Playwright
24
+ 1.64.1 installs it on Node 26 as it does on Node 22 and 24.
25
+
26
+ Playwright 1.60 to 1.64 change some behavior that ui tests may rely on:
27
+
28
+ - Device descriptors now set `screen`, so `window.screen` and media queries
29
+ see the emulated size.
30
+ - Actions, `isVisible()`, and `toBeVisible()` treat elements inside hidden
31
+ iframes as hidden.
32
+ - In headless mode, the clipboard is separate from the host's clipboard.
33
+ - `locator.ariaRef()`, the `handle` option of `exposeBinding`, and the
34
+ `videosPath` and `videoSize` context options are gone.
35
+ - `testkit setup playwright --with-deps` no longer supports Ubuntu 20.04 or
36
+ Debian 11.
37
+
38
+ [Playwright's release notes](https://playwright.dev/docs/release-notes) list
39
+ every change.
40
+
1
41
  # 0.6.3
2
42
 
3
43
  ## A global Testkit runs the product's own install
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gfdelarue/testkit",
3
- "version": "0.6.3",
3
+ "version": "0.6.4",
4
4
  "description": "Test orchestration platform for product acceptance and QA workflows",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",
@@ -96,7 +96,7 @@
96
96
  ],
97
97
  "devDependencies": {
98
98
  "@eslint/js": "^10.0.1",
99
- "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1",
99
+ "@homebridge/node-pty-prebuilt-multiarch": "^0.15.0",
100
100
  "@kitschpatrol/tokei": "2.0.2",
101
101
  "@moontern-labs/code-budget": "0.1.3",
102
102
  "@types/picomatch": "4.0.3",
@@ -113,7 +113,7 @@
113
113
  "verdaccio": "6.10.3"
114
114
  },
115
115
  "dependencies": {
116
- "@playwright/test": "1.59.1",
116
+ "@playwright/test": "1.64.1",
117
117
  "@types/node": "22.20.1",
118
118
  "@typescript-eslint/parser": "8.65.0",
119
119
  "@typescript-eslint/types": "8.65.0",
@@ -124,8 +124,8 @@
124
124
  "execa": "10.0.1",
125
125
  "minimatch": "10.2.6",
126
126
  "picocolors": "^1.1.1",
127
- "playwright": "1.59.1",
128
- "playwright-core": "1.59.1",
127
+ "playwright": "1.64.1",
128
+ "playwright-core": "1.64.1",
129
129
  "string-width": "^8.1.0",
130
130
  "tar": "7.5.21",
131
131
  "tinyglobby": "0.2.17",
@@ -151,7 +151,7 @@
151
151
  }
152
152
  },
153
153
  "engines": {
154
- "node": ">=22.15.0 <23.0.0 || >=24.14.1 <25.0.0",
154
+ "node": ">=22.15.0 <23.0.0 || >=24.14.1 <25.0.0 || >=26.13.0 <27.0.0",
155
155
  "npm": ">=11.6.0 <12.0.0"
156
156
  },
157
157
  "volta": {