@gfdelarue/testkit 0.6.2 → 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.
- package/bin/testkit.mjs +7 -5
- package/dist/app/product-handoff.js +1 -0
- package/dist/cli/product-handoff.js +2 -0
- package/dist/engines/adapters/execution.js +1 -1
- package/dist/engines/playwright/adapter/adapter.js +1 -1
- package/dist/engines/playwright/adapter/config.js +11 -3
- package/dist/engines/playwright/adapter/evidence.js +2 -2
- package/dist/engines/playwright/runtime/index.js +1 -1
- package/dist/engines/playwright/runtime/sandbox.js +1 -1
- package/dist/process/environment.js +1 -1
- package/dist/process/execa-bundle.json +1 -1
- package/dist/process/product-cli.js +1 -0
- package/dist/toolchains/host-node-support.js +1 -1
- package/dist/types/playwright.d.ts +2 -7
- package/docs/engines.md +1 -1
- package/docs/release-notes.md +58 -0
- package/package.json +7 -7
package/bin/testkit.mjs
CHANGED
|
@@ -32,11 +32,13 @@ if (!hostNodeIsSupported(process.versions.node)) {
|
|
|
32
32
|
const versionMismatch = versionDrift.checkCliVersionDrift(effectiveArgs, {
|
|
33
33
|
binUrl: import.meta.url,
|
|
34
34
|
});
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
})
|
|
39
|
-
|
|
35
|
+
const json = versionDrift.isJsonMode(effectiveArgs);
|
|
36
|
+
let handedOff = false;
|
|
37
|
+
if (versionMismatch) {
|
|
38
|
+
const { handOffToProductCli } = await import("../dist/cli/product-handoff.js");
|
|
39
|
+
handedOff = await handOffToProductCli(versionMismatch, process.argv.slice(2), { json });
|
|
40
|
+
}
|
|
41
|
+
if (!handedOff && !versionDrift.emitCliVersionDriftAndExit(versionMismatch, { json })) {
|
|
40
42
|
const { executeCli } = await import("../dist/cli/index.js");
|
|
41
43
|
await executeCli(
|
|
42
44
|
effectiveArgs,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import o from"node:fs";import e from"node:path";import{isUnknownRecord as l}from"../kernel/unknown-values.js";import{runProductCli as u}from"../process/product-cli.js";export async function runInstalledProductCli(r,n,{onStart:i}){const t=s(r);return t===null?!1:(i(),await u(t,n),!0)}function s(r){let n;try{n=JSON.parse(o.readFileSync(e.join(r,"package.json"),"utf8"))}catch{return null}if(!l(n))return null;const i=l(n.bin)?n.bin.testkit:n.bin;if(typeof i!="string")return null;const t=e.resolve(r,i);return o.statSync(t,{throwIfNoEntry:!1})?.isFile()?t:null}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{runInstalledProductCli as i}from"../app/product-handoff.js";export async function handOffToProductCli(t,e,{json:n=!1}={}){const{packageRoot:r,version:o}=t.product;return r===null?!1:await i(r,e,{onStart:()=>{n||process.stderr.write(`Using the product's @gfdelarue/testkit ${String(o)} (${r}) instead of ${t.current.version}
|
|
2
|
+
`)}})}
|
|
@@ -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,
|
|
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")),
|
|
1
|
+
import{makeTempDirAsync as D}from"../../../process/files/temp-dir.js";import{toError as g}from"../../../kernel/error-serialization.js";import{classifyManagedProcessResult as A,terminalOutcome as T,processOutputEvidence as N,failureFromError as P,firstOutputLine as R,processExitDetails as M,processExitFailure as b,executeEngineTasks as C,managedProcessFailure as x,emptyEvidence as W,taskTimeoutFailure as U}from"../../adapters/execution.js";import{requireRootDirectory as B,requireAbsoluteWorkspacePath as O,requireConsumerFile as H,safeSegment as S}from"../../adapters/workspace.js";import u from"node:fs/promises";import Y from"node:path";import{setTimeout as $}from"node:timers/promises";import{pathToFileURL as G}from"node:url";import{isEngineTaskDeadlineExceededReason as K,resolveEngineTaskInputs as V}from"../../api/index.js";import{createNativeInvocationProcessEnvironment as j,runManagedProcess as J}from"../../../process/index.js";import{PlaywrightConsumerConfigurationError as z,playwrightFileSuiteGrep as X,writePlaywrightTaskWorkspace as Q}from"./config.js";import{collectPlaywrightEvidence as Z}from"./evidence.js";import{engineEvidenceIdentity as q}from"../../adapters/evidence-identity.js";import{getNodeStartupCacheEnvironment as rr}from"../../adapters/node-startup-cache.js";import{CHROMIUM_STARTUP_ENVIRONMENT as tr}from"../runtime/chromium-environment.js";const y={codePrefix:"PLAYWRIGHT",label:"Playwright"};export const PLAYWRIGHT_SPAWN_ENVIRONMENT_MAX_BYTES=24*1024;const F=64;class L extends Error{constructor(r){const t=g(r);super(t.message,{cause:t}),this.name="PlaywrightConsumerPreparationError"}}export const playwrightAdapterDependencies={createInvocationEnvironment:j,removeOwnedDirectory:or,writeTaskWorkspace:Q};const er=new Set(["EBUSY","ENOTEMPTY","EPERM"]),ir=5;async function or(e){for(let r=0;;r+=1)try{await u.rm(e,{force:!0,recursive:!0});return}catch(t){const i=t instanceof Error&&"code"in t?t.code:void 0;if(r===ir||typeof i!="string"||!er.has(i))throw t;await $((r+1)*100)}}export async function executePlaywrightEngine(e,r,t,i=playwrightAdapterDependencies){const o=await nr(e,r,i,t);return C(e,o.tasks,y,(a,s)=>ar(e,o,a,s),pr(e,o.installation))}async function nr(e,r,t,i){const o=await B(e.workspace.rootDirectory);O(e.workspace.temporaryDirectory,"temporary directory"),O(e.workspace.artifactDirectory,"artifact directory"),await u.mkdir(e.workspace.temporaryDirectory,{recursive:!0}),await u.mkdir(e.workspace.artifactDirectory,{recursive:!0});const a=await u.realpath(e.workspace.temporaryDirectory),s=await u.realpath(e.workspace.artifactDirectory),n=await D(`playwright-${S(e.invocationId)}-`,{parentDir:a});i.defer(()=>t.removeOwnedDirectory(n));const l=await t.createInvocationEnvironment(n);i.defer(()=>t.removeOwnedDirectory(l.temporaryDirectory));const p=[];for(const[h,c]of e.tasks.entries()){const I=V(e,c),v=`${h.toString().padStart(3,"0")}-${S(c.id)}`,_=Y.join(n,v),f=await D(`${v}-`,{parentDir:s});try{const m=lr(e,c.serviceName),E=await ur(()=>H(o,c.file,"Playwright task file")),w=await t.writeTaskWorkspace({artifactDirectory:f,baseUrl:m,browserFile:r.browserExecutable,databases:e.target.databases,endpoints:e.target.endpoints,handles:e.target.handles,inputs:I,invocationId:e.invocationId,managedModuleEntrypoints:{authoring:r.authoringEntrypoints.authoring,cli:r.family.cli,environment:r.authoringEntrypoints.environment,managedTest:r.authoringEntrypoints.managedTest,playwright:r.family.playwright.entrypoint,playwrightCore:r.family.playwrightCore.entrypoint,playwrightTest:r.family.playwrightTest.entrypoint,root:r.authoringEntrypoints.root,rootPackageJson:r.authoringEntrypoints.rootPackageJson},moduleFile:r.family.playwrightTest.entrypoint,orchestration:c.orchestration,rootDirectory:o,serviceName:c.serviceName,sourceFile:E,taskDirectory:_,taskId:c.id});i.defer(async()=>{try{await t.removeOwnedDirectory(w.attachmentDirectory)}catch(d){if(!(d instanceof Error&&"code"in d&&d.code==="ENOTDIR"))throw d}}),p.push({artifactDirectory:f,inputs:I,task:c,preparationFailure:null,prepared:{baseUrl:m,sourceFile:E,workspace:w}})}catch(m){const E=(await yr([_,f],t)).map(w=>w.error);p.push({artifactDirectory:f,inputs:I,task:c,preparationFailure:playwrightPreparationFailure(m,E),prepared:null})}}return Object.freeze({installation:r,invocationEnvironment:l.environment,rootDirectory:o,tasks:Object.freeze(p)})}async function ar(e,r,t,i){const a={...await rr(e.workspace.cacheDirectory,t.inputs.environment),...sr(e,r,t)};try{k(a)}catch{delete a.NODE_COMPILE_CACHE}const s=t.task.deadlineMs-Date.now();if(s<=0)return T(t.task.id,"failed",i,W(),U(t.task.timeoutMs,y));const n=await J({args:[r.installation.family.cli,"test","--grep",X(t.prepared.sourceFile),"--config",t.prepared.workspace.configFile],cwd:r.rootDirectory,displayLabel:"Playwright",env:a,executable:process.execPath,signal:e.signal,timeoutMs:s});let l;try{l=await Z({identity:{engineId:"playwright",invocationId:e.invocationId,taskId:t.task.id,sourceFile:t.prepared.sourceFile},artifactDirectory:t.artifactDirectory,outputDirectory:t.prepared.workspace.outputDirectory,reportFile:t.prepared.workspace.reportFile,reportersFinishedFile:t.prepared.workspace.reportersFinishedFile,rootDirectory:r.rootDirectory,sourceFile:t.prepared.sourceFile,stderr:n.stderr,stdout:n.stdout})}catch(h){return T(t.task.id,"failed",i,N(n),playwrightEvidenceCollectionFailure(n,h))}const p=classifyPlaywrightProcessResult(n,l,t.task.timeoutMs,K(e.signal.reason));return T(t.task.id,p.status,i,l.evidence,p.failure)}export function classifyPlaywrightProcessResult(e,r,t,i){const o=A(e,t,i,y);if(o!==null)return o;if(r.outputLimit!==null){const n=r.outputLimit;return{failure:{code:"PLAYWRIGHT_OUTPUT_ARTIFACT_LIMIT",details:{...n},message:`Playwright output exceeded its ${n.limit.toString()} ${n.kind==="bytes"?"byte aggregate":"entry"} artifact limit`},status:"failed"}}const a=r.attachmentFailures.find(n=>n.code==="PLAYWRIGHT_ATTACHMENT_LIMIT");if(a!==void 0)return{failure:a,status:"failed"};if(r.report.failure!==null){const n=r.report.failure;return{...r.report,failure:{...n,details:{...n.details,evidenceErrors:r.reportError===null?0:1,...M(e)}}}}const s=r.attachmentFailures.at(0);return s!==void 0?{failure:s,status:"failed"}:e.exitCode!==0&&(r.reportError===null||!r.reportersFinished)?b(e,y,R(e.stderr)??R(e.stdout)):r.reportError!==null?{failure:{...P(r.reportError,"PLAYWRIGHT_REPORT_INVALID"),classification:"infra"},status:"failed"}:r.report}export function playwrightEvidenceCollectionFailure(e,r){const t=g(r);return x(e,y,t)??{...P(t,"PLAYWRIGHT_EVIDENCE_FAILED"),classification:"infra"}}function sr(e,r,t){const i={...r.invocationEnvironment},o=t.task,a=t.prepared.workspace.inputBootstrapFile,s=t.prepared.workspace.resolutionBootstrapFile;return Object.assign(i,{...t.prepared.baseUrl===null?{}:{BASE_URL:t.prepared.baseUrl},...tr,FORCE_COLOR:"0",NO_COLOR:"1",PLAYWRIGHT_BROWSERS_PATH:r.installation.registry.canonicalValue,PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD:"1",TESTKIT_ACTIVE:"1",TESTKIT_INTERNAL_FILE_DEADLINE_MS:String(o.deadlineMs),TESTKIT_INTERNAL_FILE_TIMEOUT_SECONDS:String(Math.max(1,Math.ceil(o.timeoutMs/1e3))),TESTKIT_INVOCATION_ID:e.invocationId,TESTKIT_LEASE_DIR:o.orchestration.lease.directory,TESTKIT_LEASE_ID:o.orchestration.lease.id,TESTKIT_MODE:o.orchestration.mode,...o.orchestration.runtimeId===void 0?{}:{TESTKIT_RUNTIME_ID:o.orchestration.runtimeId},TESTKIT_TEST_FILE:o.file,TESTKIT_TEST_ID:o.id}),i.NODE_OPTIONS=cr(s,a),k(i),i}function cr(e,r){return["--experimental-strip-types",`--require=${JSON.stringify(e)}`,`--import=${JSON.stringify(G(r).href)}`].join(" ")}export function playwrightSpawnEnvironmentBytes(e){return Object.entries(e).reduce((r,[t,i])=>r+(process.platform==="win32"?(t.length+i.length+2)*2:Buffer.byteLength(t,"utf8")+Buffer.byteLength(i,"utf8")+2),0)}function k(e){if(Object.keys(e).length>F)throw new Error(`Playwright physical spawn environment exceeds its ${F.toString()} entry limit`);if(playwrightSpawnEnvironmentBytes(e)>PLAYWRIGHT_SPAWN_ENVIRONMENT_MAX_BYTES)throw new Error(`Playwright physical spawn environment exceeds its ${PLAYWRIGHT_SPAWN_ENVIRONMENT_MAX_BYTES.toString()} byte limit`)}function lr(e,r){const t=e.target.baseUrl;if(t===null)return null;if(typeof t!="string"||t.trim().length===0)throw new Error(`Playwright task service ${r} endpoint must be a non-empty URL`);let i;try{i=new URL(t)}catch(o){throw new Error(`Playwright task service ${r} endpoint is not a valid URL`,{cause:o})}if(i.protocol!=="http:"&&i.protocol!=="https:")throw new Error(`Playwright task service ${r} endpoint must use http or https`);return i.href}function pr(e,r){return{...q({engineId:"playwright",installationKind:"root-playwright-family",profile:e.profile}),browserName:"chromium",packages:{playwright:r.family.playwright.version,playwrightCore:r.family.playwrightCore.version,playwrightTest:r.family.playwrightTest.version},registryMode:r.registry.mode}}export function playwrightPreparationFailure(e,r){if(r.length===0)return{...P(e,"PLAYWRIGHT_PREPARE_FAILED"),classification:e instanceof L||e instanceof z?"product":"infra"};const t=g(e);return{classification:"infra",code:"PLAYWRIGHT_PREPARE_CLEANUP_FAILED",details:{cleanupFailures:r.map(i=>i.message),preparationFailure:t.message},message:`Playwright task preparation failed and its partial workspace could not be removed: ${r.map(i=>i.message).join("; ")}`,...t.stack===void 0?{}:{stack:t.stack}}}async function ur(e){try{return await e()}catch(r){throw new L(r)}}async function yr(e,r){const t=[];for(const i of e)try{await r.removeOwnedDirectory(i)}catch(o){t.push({directory:i,error:g(o)})}return t}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{ownedRuntimeInputModuleUrl as
|
|
1
|
+
import{ownedRuntimeInputModuleUrl as b}from"../../runtime-input-file.js";import P from"node:crypto";import s from"node:fs/promises";import o from"node:path";import{fileURLToPath as j,pathToFileURL as 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:
|
|
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("[\\\\/]")}
|
|
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
|
|
2
|
-
${p}`},status:"failed"}}return o.length>0?{failure:null,status:o.every(p=>p.status==="skipped")?"skipped":"passed"}:{failure:{code:"PLAYWRIGHT_NO_TESTS",details:{file:U(e,r),reportedSpecs:t.specs.slice(0,50),stats:t.stats},message:"Selected Playwright task did not execute any tests"},status:"failed"}}async function Wt(t){try{return(await T.lstat(t)).isFile()}catch{return!1}}function ot(t,e){return w.relative(w.normalize(t),e)===""}function Ut(t,e){const r=S(t,"Playwright native report result"),a=Ct.find(y=>y===r.status);if(a===void 0)throw new Error("Playwright native report status is invalid");if(typeof r.failOnFlakyTests!="boolean")throw new Error("Playwright native report failOnFlakyTests must be a boolean");const n=r.failOnFlakyTests,s=qt(r.stats),o=G(r.errors,"errors"),h=o.length,u=rt(r.environmentFailure,"Playwright native report environment failure"),p=u===null?[]:[u];let f=u!==null;const i=[],l=[],m=[],E=new Set,c=new Map,g={expected:0,flaky:0,skipped:0,unexpected:0};for(const y of A(r.specs,"specs")){const d=S(y,"Playwright native report spec"),R=x(d.id,"Playwright native spec.id");if(E.has(R))throw new Error("Playwright native report contains a duplicate test identity");E.add(R);const k=x(d.title,"Playwright native spec.title"),v=G(d.parents,"spec.parents");v.length>0&&(v[0]=et(v[0]));const j=et(x(d.file,"Playwright native spec.file")),O=x(d.sourceFile,"Playwright native spec.sourceFile");if(!w.isAbsolute(O)&&!w.win32.isAbsolute(O))throw new Error("Playwright native spec.sourceFile must be absolute");const dt=B(d.projectName,"spec.projectName"),P=d.status;if(P!=="expected"&&P!=="flaky"&&P!=="skipped"&&P!=="unexpected")throw new Error("Playwright native spec.status is invalid");const K=Dt.find(ft=>ft===d.expectedStatus);if(K===void 0)throw new Error("Playwright native spec.expectedStatus is invalid");g[P]+=1;const V=G(d.failureMessages,"spec.failureMessages"),J=d.failureStack===null?null:B(d.failureStack,"spec.failureStack"),N=tt(d.line,"Playwright native spec.line"),M=tt(d.column,"Playwright native spec.column"),C=ot(e,O),$=rt(d.environmentFailure,"Playwright native spec environment failure");$!==null&&(f=!0),C&&$!==null&&p.push($),P==="unexpected"||P==="flaky"&&n?(o.push(...V),m.push({...$===null?{}:{classification:"infra"},kind:C?"playwright-spec":"playwright-other-file-spec",key:[...v.slice(1),k].filter(Boolean).join(" "),title:k,count:1,message:V[0]??`Playwright reported "${k}" as an unexpected failure`,...v.length===0?{}:{suitePath:v},location:{path:j,...N===null?{}:{line:N},...M===null?{}:{column:M}},...J===null?{}:{stack:J}})):C&&P==="skipped"&&K!=="skipped"&&m.push({kind:"playwright-not-run",key:[...v.slice(1),k].filter(Boolean).join(" "),title:k,message:`Playwright did not run "${k}", which was expected to run`,...v.length===0?{}:{suitePath:v},location:{path:j,...N===null?{}:{line:N},...M===null?{}:{column:M}}}),l.push(O);const Q=b(d.retries,"Playwright native spec.retries");c.set(R,{retry:Q,status:zt[P],title:[...v.slice(1),k].join(" \u203A ")}),i.push({durationMs:q(d.durationMs,"Playwright native spec.durationMs"),retries:Q,file:j,projectName:dt,status:P,title:[...v,k].filter(Boolean).join(" > ")})}for(const y of["expected","flaky","skipped","unexpected"])if(s[y]!==g[y])throw new Error("Playwright native report stats do not match its cases");const F=A(r.logs,"logs").map(y=>{const d=S(y,"Playwright native report log");if(d.stream!=="stdout"&&d.stream!=="stderr")throw new Error("Playwright native report log stream is invalid");return{stream:d.stream,text:B(d.text,"log.text")}});return{attachments:A(r.attachments,"attachments").map((y,d)=>Xt(y,d,E)),carriesEnvironmentFailures:f,environmentFailures:p,failOnFlakyTests:n,failureDetails:m,failureMessages:o,logs:te(F),reportErrors:h,runStatus:a,sourceFiles:l,specs:i,stats:s,tests:c}}function Yt(t){const e=S(t,"Testkit native Playwright report"),r=ct(S(e.result,"Playwright native report result")),a=A(r.specs,"specs").map(n=>ct(S(n,"Playwright native report spec")));return JSON.stringify({...e,result:{...r,specs:a}})}function ct(t){return Object.fromEntries(Object.entries(t).filter(([e])=>e!=="environmentFailure"))}function A(t,e){if(!Array.isArray(t))throw new Error(`Playwright native report ${e} must be an array`);return t}function G(t,e){return A(t,e).map(r=>B(r,e))}function B(t,e){if(typeof t!="string")throw new Error(`Playwright native report ${e} must be a string`);return t}function Xt(t,e,r){const a=S(t,`Playwright attachment[${e.toString()}]`),n=x(a.specId,"Playwright attachment.specId");if(!r.has(n))throw new Error("Playwright native attachment references an unknown test identity");const s=W(a.path),o=a.error===void 0?void 0:Gt(a.error);if(a.body!==void 0||s===null&&o===void 0)throw new Error("Playwright attachment must contain a path or an error");return{contentType:W(a.contentType)??"application/octet-stream",name:W(a.name)??`attachment-${e.toString()}`,specId:n,...s===null?{}:{path:s},...o===void 0?{}:{error:o},retry:b(a.retry,"Playwright attachment.retry"),specTitle:x(a.specTitle,"Playwright attachment.specTitle")}}function Gt(t){const e=S(t,"Playwright attachment.error");if(e.kind==="io")return{kind:"io",message:x(e.message,"Playwright attachment.error.message")};const r=e.kind;if(r!=="individual"&&r!=="total")throw new Error("Playwright attachment.error has an invalid kind");const a=r==="individual"?nt:At,n=b(e.observed,"Playwright attachment.error.observed");if(e.limit!==a||n<=a)throw new Error("Playwright attachment.error has an invalid limit");return{kind:r,limit:a,observed:n}}function L(t,e){const{name:r,specId:a,specTitle:n,retry:s}=t,o=e.kind==="io"?e.message:`exceeds its ${e.limit.toString()} byte ${e.kind} limit (observed ${e.observed.toString()} bytes)`;return{code:e.kind==="io"?"PLAYWRIGHT_ATTACHMENT_FAILED":"PLAYWRIGHT_ATTACHMENT_LIMIT",...e.kind==="io"?{classification:"infra"}:{},details:{name:r,specId:a,specTitle:n,retry:s,...e},message:`Playwright attachment "${r}" (${n}, retry ${s.toString()}): ${o}`}}function lt(t){return t.name===St&&t.error===void 0}async function Kt(t,e,r,a,n,s){const o=[],h=new Set,u=[];for(const i of n.attachments.filter(lt))try{const l=await ut(i.path,r,t,s);h.add(l.file);const m=n.tests.get(i.specId);if(m===void 0||i.retry!==m.retry)continue;const{contents:E}=await yt(l.file,Ft,"Browser network record",s,l.identity);u.push({title:m.title,status:m.status,retry:m.retry,...xt(E)})}catch(l){o.push(L(i,it(l)))}if(u.length===0)return{artifacts:[],failures:o,sourcePaths:h};const p=_t(a,u),f=[];for(const[i,l,m,E]of[["browser-network",p.json,"application/json",kt],["browser-network-report",p.markdown,"text/markdown",bt]]){const c=z(Buffer.from(l,"utf8")),g=await H(e,`${i}${m==="text/markdown"?".md":".json"}`,c.contents);f.push(I(g,c,m,E,i,p.summary))}return{artifacts:f,failures:o,sourcePaths:h}}async function Vt(t,e,r,a,n){const s=[],o=[],h=new Set;let u=0;const p=w.join(e,"attachments");a.length>0&&await T.mkdir(p,{mode:448,recursive:!1});for(const[f,i]of a.entries())try{if(i.error!==void 0){i.path!==void 0&&h.add(w.resolve(r,i.path)),o.push(L(i,i.error));continue}const l=await ut(i.path,r,t,n);h.add(l.file);const m=b(Number(l.identity.size),"Playwright attachment size"),E=Rt(m,u+m);if(E!==null){o.push(L(i,E));continue}const c=await Z(l.file,nt,"Playwright attachment",n,l.identity,p,`${f.toString().padStart(4,"0")}-${pt(i.name)}${ee(i)}`);u+=c.identity.sizeBytes,s.push({path:c.filePath,contentType:i.contentType,kind:"engine.playwright-attachment",name:i.name,summary:`${i.specTitle} retry ${i.retry.toString()}`,metadata:{...c.identity,specId:i.specId,specTitle:i.specTitle,retry:i.retry}})}catch(l){o.push(L(i,it(l)))}return{artifacts:s,failures:o,sourcePaths:h}}async function Jt(t,e,r,a,n){try{const s=await n.lstat(r);if(!s.isDirectory()||s.isSymbolicLink())throw new Error("Playwright output directory must be a non-symbolic-link directory");const o=await T.realpath(r);if(!D(t,o))throw new Error("Playwright output directory escaped the artifact root");const h=await Qt(o,a,n),u=[],p=w.join(e,"output");h.candidates.length>0&&await T.mkdir(p,{mode:448,recursive:!1});for(const[f,i]of h.candidates.entries()){const l=await Z(i.file,Y,"Playwright output file",n,i.identity,p,`${f.toString().padStart(5,"0")}-${pt(w.basename(i.file))}${ie(i.file)}`);u.push(I(l.filePath,l.identity,re(i.file),"engine.playwright-output",i.relativePath,"Playwright output"))}return{artifacts:u,bytes:h.bytes,entries:h.entries}}catch(s){if(gt(s))return{artifacts:[],bytes:0,entries:0};throw s}}async function Qt(t,e,r){const a=[];let n=0,s=0;const o=[{depth:0,directory:t}];for(;o.length>0;){const h=o.pop();if(h===void 0)continue;if(h.depth>at)throw new Error(`Playwright output exceeds the ${at.toString()} level directory depth limit`);const u=h.directory,p=await T.readdir(u,{withFileTypes:!0});p.sort((f,i)=>f.name.localeCompare(i.name));for(const f of p){if(s+=1,s>st)throw new X({bytes:n,entries:s,kind:"entries",limit:st,observed:s});const i=w.join(u,f.name),l=await r.lstat(i);if(l.isSymbolicLink())throw new Error("Playwright output must not contain symbolic links");if(l.isDirectory()){o.push({depth:h.depth+1,directory:i});continue}if(!l.isFile())continue;if(l.size<0n||l.size>BigInt(Number.MAX_SAFE_INTEGER))throw new Error("Playwright output file has an invalid size");const m=Number(l.size);if(n+=m,!Number.isSafeInteger(n)||n>Y)throw new X({bytes:n,entries:s,kind:"bytes",limit:Y,observed:n});e.has(i)||a.push({file:i,identity:l,relativePath:U(t,i)})}}return{bytes:n,candidates:a,entries:s}}function Zt(t){return t.kind==="bytes"?`Playwright output exceeds its ${t.limit.toString()} byte aggregate limit (observed ${t.observed.toString()} bytes)`:`Playwright output exceeds its ${t.limit.toString()} entry limit (observed ${t.observed.toString()} entries)`}async function ut(t,e,r,a){if(t===void 0)throw new Error("Playwright path attachment did not declare a path");const n=w.resolve(e,t),s=await a.lstat(n);if(!s.isFile()||s.isSymbolicLink())throw new Error("Playwright attachment path must be a regular non-symbolic-link file");const o=await T.realpath(n);if(!D(e,o)&&!D(r,o))throw new Error("Playwright attachment path must stay within the consumer or artifact root");return{file:o,identity:s}}function qt(t){const e=S(t,"Playwright report.stats");return{expected:b(e.expected,"Playwright stats.expected"),unexpected:b(e.unexpected,"Playwright stats.unexpected"),flaky:b(e.flaky,"Playwright stats.flaky"),skipped:b(e.skipped,"Playwright stats.skipped"),duration:q(e.duration,"Playwright stats.duration")}}function te(t){let e=Nt;const r=[];for(const a of t){if(e<=0)break;const s=Buffer.from(a.text,"utf8").subarray(0,e).toString("utf8");s.length>0&&r.push({...a,text:s}),e-=Buffer.byteLength(s)}return e<=0&&r.push({stream:"engine",text:"Playwright report log evidence was truncated at its bounded limit"}),r}function ee(t){if(t.path!==void 0){const e=w.extname(t.path);if(/^\.[a-z0-9]{1,12}$/iu.test(e))return e}return t.contentType==="application/json"?".json":t.contentType.startsWith("text/")?".txt":t.contentType==="image/png"?".png":t.contentType==="image/jpeg"?".jpg":".bin"}function re(t){const e=w.extname(t).toLowerCase();return e===".json"?"application/json":e===".zip"?"application/zip":e===".png"?"image/png":e===".jpg"||e===".jpeg"?"image/jpeg":e===".webm"?"video/webm":[".log",".txt",".md"].includes(e)?"text/plain; charset=utf-8":"application/octet-stream"}function ie(t){const e=w.extname(t).toLowerCase();return/^\.[a-z0-9]{1,12}$/u.test(e)?e:".bin"}function pt(t){const e=t.trim().toLowerCase().replace(/[^a-z0-9._-]+/gu,"-").replace(/^-+|-+$/gu,"").slice(0,80);return e.length>0?e:"attachment"}
|
|
1
|
+
import{toError as ht}from"../../../kernel/error-serialization.js";import{isPathWithin as D}from"../../../process/files/paths.js";import k from"node:fs/promises";import w from"node:path";import{evidenceArtifact as I,evidenceFileAccess as mt,readStableEvidenceFile as yt,readStableJsonFile as wt,snapshotStableEvidenceFile as Z,stableEvidenceFromBuffer as z,writeOwnedEvidenceFile as H,isMissingFileError as gt}from"../../adapters/evidence-files.js";import{materializePlaywrightFailureDetails as Et}from"./failure-details.js";import{nonNegativeInteger as F,nonNegativeNumber as q,optionalNonEmptyString as W,optionalNonNegativeInteger as tt,portableRelativePath as U,portableReportPath as et,requireNonEmptyString as _,requireRecord as b}from"./evidence-validation.js";import{readNativeReport as vt}from"../../adapters/native-report.js";import{environmentValueMissingFailure as Pt,parseEnvironmentValueMissingFailure as rt,readEnvironmentValueMissingFailure as Tt}from"../../contracts/environment-values.js";import{BROWSER_NETWORK_ATTACHMENT as St}from"../runtime/browser-network.js";import{BROWSER_NETWORK_ARTIFACT_KIND as kt,BROWSER_NETWORK_REPORT_KIND as bt,MAX_BROWSER_NETWORK_RECORD_BYTES as Ft,readBrowserNetworkRecord as xt,renderBrowserNetwork as _t}from"./browser-network-evidence.js";import{attachmentLimitError as Rt,attachmentIoError as it,MAX_ATTACHMENT_BYTES as nt,MAX_TOTAL_ATTACHMENT_BYTES as At}from"./attachments.js";const Ot=64*1024*1024,st=1e4,Y=512*1024*1024,Nt=2*1024*1024,at=128,Mt=".testkit-playwright-evidence",$t=/(?:^|\n)(?:Error:\s*)?No tests found(?:\.|\n|$)/u,It=/browserType\.launch(?:PersistentContext)?:/u,Bt=/browserType\.launch(?:PersistentContext|Server)?:|Failed to launch browser/u,Lt=/Chromium sandboxing failed!|No usable sandbox!|crbug\.com\/(?:357670|638180)/u,jt="Testkit starts managed Chromium with its sandbox on, and this host can't start it. To run without the sandbox here, set use.launchOptions.chromiumSandbox to false in playwright.config.ts, or in one file with test.use.",Ct=["failed","interrupted","passed","timedout"],Dt=["failed","interrupted","passed","skipped","timedOut"],zt={expected:"passed",flaky:"flaky",skipped:"skipped",unexpected:"failed"};class X extends Error{limit;constructor(e){super(Zt(e)),this.limit=e,this.name="PlaywrightOutputArtifactLimitError"}}export async function collectPlaywrightEvidence(t,e=mt){await k.mkdir(t.artifactDirectory,{recursive:!0});const r=await k.realpath(t.artifactDirectory),a=await k.realpath(t.rootDirectory),n=w.join(r,Mt);await k.mkdir(n,{mode:448,recursive:!1});const s=[],o=[];for(const u of["stdout","stderr"]){const P=t[u];if(P.length===0)continue;s.push({stream:u,text:P});const R=z(Buffer.from(P,"utf8")),y=await H(n,`${u}.log`,R.contents);o.push(I(y,R,"text/plain; charset=utf-8","engine.process-output",`playwright-${u}`,`${R.sizeBytes.toString()} bytes`))}let h=null,l=null,p=new Set,f=[];try{const u=await wt(t.reportFile,Ot,"Testkit native Playwright report",e);if(u.file===null)h=new Error("Playwright did not produce its Testkit native report");else{l=Ut(vt(u.value,t.identity).result,t.sourceFile);const P=l.carriesEnvironmentFailures?z(Buffer.from(Yt(u.value))):u.file,R=await H(n,"testkit-playwright-report.json",P.contents);o.push(I(R,P,"application/json","engine.testkit-playwright-report","testkit-playwright-report","Testkit native Playwright report")),s.push(...l.logs);const y=await Et(n,l.failureDetails);y!==null&&o.push(y);const d=await Kt(r,n,a,U(a,t.sourceFile),l,e),x=await Vt(r,n,a,l.attachments.filter(T=>!lt(T)),e);o.push(...d.artifacts,...x.artifacts),p=new Set([...d.sourcePaths,...x.sourcePaths]),f=[...d.failures,...x.failures]}}catch(u){h=ht(u)}let i={artifacts:[],bytes:0,entries:0},c=null;try{i=await Jt(r,n,t.outputDirectory,p,e),o.push(...i.artifacts)}catch(u){if(!(u instanceof X))throw u;c=u.limit,s.push({stream:"engine",text:u.message})}const m=await Wt(t.reportersFinishedFile),g=Ht(l,a,t.sourceFile,f.length,m),E=c??i;return{attachmentFailures:f,evidence:{artifacts:o,logs:s,metrics:[...Object.entries(l?.stats??{}).map(([u,P])=>({name:u==="duration"?"playwright.duration":`playwright.tests.${u}`,unit:u==="duration"?"milliseconds":"tests",value:P})),...["bytes","entries"].map(u=>({name:`playwright.output.${u}`,unit:u,value:E[u]}))],...l===null?{}:{native:{playwright:{attachmentFailures:f.map(u=>({...u})),specs:l.specs,stats:l.stats}}}},outputLimit:c,report:g,reportError:h,reportersFinished:m}}function Ht(t,e,r,a,n){if(t===null)return{failure:null,status:"failed"};const s=t.environmentFailures.at(0);if(s!==void 0)return{failure:Pt(t.environmentFailures.flatMap(p=>Tt(p)??[]),s.message),status:"failed"};const o=t.specs.filter((p,f)=>ot(r,t.sourceFiles[f])),h=t.failureMessages.length>0&&t.failureMessages.every(p=>$t.test(p)),l=t.failOnFlakyTests?t.stats.flaky:0;if(t.stats.unexpected+l>0||t.failureMessages.length>0&&(!h||o.length>0)){const p=t.failureMessages.find(f=>Bt.test(f)&&Lt.test(f));return{failure:{...p!==void 0||t.failureMessages.some(f=>It.test(f)&&/Executable doesn't exist|Host system is missing dependencies|error while loading shared libraries|Failed to launch/u.test(f))?{classification:"infra"}:{},code:"PLAYWRIGHT_SPECS_FAILED",details:{attachmentFailures:a,failOnFlakyTests:t.failOnFlakyTests,reportErrors:t.reportErrors,reportersFinished:n,runStatus:t.runStatus,stats:t.stats,specs:t.specs.slice(0,50)},message:p===void 0?t.failureMessages.at(0)??`${(t.stats.unexpected+l).toString()} Playwright test(s) failed`:`${jt}
|
|
2
|
+
${p}`},status:"failed"}}return o.length>0?{failure:null,status:o.every(p=>p.status==="skipped")?"skipped":"passed"}:{failure:{code:"PLAYWRIGHT_NO_TESTS",details:{file:U(e,r),reportedSpecs:t.specs.slice(0,50),stats:t.stats},message:"Selected Playwright task did not execute any tests"},status:"failed"}}async function Wt(t){try{return(await k.lstat(t)).isFile()}catch{return!1}}function ot(t,e){return w.relative(w.normalize(t),e)===""}function Ut(t,e){const r=b(t,"Playwright native report result"),a=Ct.find(y=>y===r.status);if(a===void 0)throw new Error("Playwright native report status is invalid");if(typeof r.failOnFlakyTests!="boolean")throw new Error("Playwright native report failOnFlakyTests must be a boolean");const n=r.failOnFlakyTests,s=qt(r.stats),o=G(r.errors,"errors"),h=o.length,l=rt(r.environmentFailure,"Playwright native report environment failure"),p=l===null?[]:[l];let f=l!==null;const i=[],c=[],m=[],g=new Set,E=new Map,u={expected:0,flaky:0,skipped:0,unexpected:0};for(const y of A(r.specs,"specs")){const d=b(y,"Playwright native report spec"),x=_(d.id,"Playwright native spec.id");if(g.has(x))throw new Error("Playwright native report contains a duplicate test identity");g.add(x);const T=_(d.title,"Playwright native spec.title"),v=G(d.parents,"spec.parents");v.length>0&&(v[0]=et(v[0]));const j=et(_(d.file,"Playwright native spec.file")),O=_(d.sourceFile,"Playwright native spec.sourceFile");if(!w.isAbsolute(O)&&!w.win32.isAbsolute(O))throw new Error("Playwright native spec.sourceFile must be absolute");const dt=B(d.projectName,"spec.projectName"),S=d.status;if(S!=="expected"&&S!=="flaky"&&S!=="skipped"&&S!=="unexpected")throw new Error("Playwright native spec.status is invalid");const K=Dt.find(ft=>ft===d.expectedStatus);if(K===void 0)throw new Error("Playwright native spec.expectedStatus is invalid");u[S]+=1;const V=G(d.failureMessages,"spec.failureMessages"),J=d.failureStack===null?null:B(d.failureStack,"spec.failureStack"),N=tt(d.line,"Playwright native spec.line"),M=tt(d.column,"Playwright native spec.column"),C=ot(e,O),$=rt(d.environmentFailure,"Playwright native spec environment failure");$!==null&&(f=!0),C&&$!==null&&p.push($),S==="unexpected"||S==="flaky"&&n?(o.push(...V),m.push({...$===null?{}:{classification:"infra"},kind:C?"playwright-spec":"playwright-other-file-spec",key:[...v.slice(1),T].filter(Boolean).join(" "),title:T,count:1,message:V[0]??`Playwright reported "${T}" as an unexpected failure`,...v.length===0?{}:{suitePath:v},location:{path:j,...N===null?{}:{line:N},...M===null?{}:{column:M}},...J===null?{}:{stack:J}})):C&&S==="skipped"&&K!=="skipped"&&m.push({kind:"playwright-not-run",key:[...v.slice(1),T].filter(Boolean).join(" "),title:T,message:`Playwright did not run "${T}", which was expected to run`,...v.length===0?{}:{suitePath:v},location:{path:j,...N===null?{}:{line:N},...M===null?{}:{column:M}}}),c.push(O);const Q=F(d.retries,"Playwright native spec.retries");E.set(x,{retry:Q,status:zt[S],title:[...v.slice(1),T].join(" \u203A ")}),i.push({durationMs:q(d.durationMs,"Playwright native spec.durationMs"),retries:Q,file:j,projectName:dt,status:S,title:[...v,T].filter(Boolean).join(" > ")})}for(const y of["expected","flaky","skipped","unexpected"])if(s[y]!==u[y])throw new Error("Playwright native report stats do not match its cases");const P=A(r.logs,"logs").map(y=>{const d=b(y,"Playwright native report log");if(d.stream!=="stdout"&&d.stream!=="stderr")throw new Error("Playwright native report log stream is invalid");return{stream:d.stream,text:B(d.text,"log.text")}});return{attachments:A(r.attachments,"attachments").map((y,d)=>Xt(y,d,g)),carriesEnvironmentFailures:f,environmentFailures:p,failOnFlakyTests:n,failureDetails:m,failureMessages:o,logs:te(P),reportErrors:h,runStatus:a,sourceFiles:c,specs:i,stats:s,tests:E}}function Yt(t){const e=b(t,"Testkit native Playwright report"),r=ct(b(e.result,"Playwright native report result")),a=A(r.specs,"specs").map(n=>ct(b(n,"Playwright native report spec")));return JSON.stringify({...e,result:{...r,specs:a}})}function ct(t){return Object.fromEntries(Object.entries(t).filter(([e])=>e!=="environmentFailure"))}function A(t,e){if(!Array.isArray(t))throw new Error(`Playwright native report ${e} must be an array`);return t}function G(t,e){return A(t,e).map(r=>B(r,e))}function B(t,e){if(typeof t!="string")throw new Error(`Playwright native report ${e} must be a string`);return t}function Xt(t,e,r){const a=b(t,`Playwright attachment[${e.toString()}]`),n=_(a.specId,"Playwright attachment.specId");if(!r.has(n))throw new Error("Playwright native attachment references an unknown test identity");const s=W(a.path),o=a.error===void 0?void 0:Gt(a.error);if(a.body!==void 0||s===null&&o===void 0)throw new Error("Playwright attachment must contain a path or an error");return{contentType:W(a.contentType)??"application/octet-stream",name:W(a.name)??`attachment-${e.toString()}`,specId:n,...s===null?{}:{path:s},...o===void 0?{}:{error:o},retry:F(a.retry,"Playwright attachment.retry"),specTitle:_(a.specTitle,"Playwright attachment.specTitle")}}function Gt(t){const e=b(t,"Playwright attachment.error");if(e.kind==="io")return{kind:"io",message:_(e.message,"Playwright attachment.error.message")};const r=e.kind;if(r!=="individual"&&r!=="total")throw new Error("Playwright attachment.error has an invalid kind");const a=r==="individual"?nt:At,n=F(e.observed,"Playwright attachment.error.observed");if(e.limit!==a||n<=a)throw new Error("Playwright attachment.error has an invalid limit");return{kind:r,limit:a,observed:n}}function L(t,e){const{name:r,specId:a,specTitle:n,retry:s}=t,o=e.kind==="io"?e.message:`exceeds its ${e.limit.toString()} byte ${e.kind} limit (observed ${e.observed.toString()} bytes)`;return{code:e.kind==="io"?"PLAYWRIGHT_ATTACHMENT_FAILED":"PLAYWRIGHT_ATTACHMENT_LIMIT",...e.kind==="io"?{classification:"infra"}:{},details:{name:r,specId:a,specTitle:n,retry:s,...e},message:`Playwright attachment "${r}" (${n}, retry ${s.toString()}): ${o}`}}function lt(t){return t.name===St&&t.error===void 0}async function Kt(t,e,r,a,n,s){const o=[],h=new Set,l=[];for(const i of n.attachments.filter(lt))try{const c=await ut(i.path,r,t,s);h.add(c.file);const m=n.tests.get(i.specId);if(m===void 0||i.retry!==m.retry)continue;const{contents:g}=await yt(c.file,Ft,"Browser network record",s,c.identity);l.push({title:m.title,status:m.status,retry:m.retry,...xt(g)})}catch(c){o.push(L(i,it(c)))}if(l.length===0)return{artifacts:[],failures:o,sourcePaths:h};const p=_t(a,l),f=[];for(const[i,c,m,g]of[["browser-network",p.json,"application/json",kt],["browser-network-report",p.markdown,"text/markdown",bt]]){const E=z(Buffer.from(c,"utf8")),u=await H(e,`${i}${m==="text/markdown"?".md":".json"}`,E.contents);f.push(I(u,E,m,g,i,p.summary))}return{artifacts:f,failures:o,sourcePaths:h}}async function Vt(t,e,r,a,n){const s=[],o=[],h=new Set;let l=0;const p=w.join(e,"attachments");a.length>0&&await k.mkdir(p,{mode:448,recursive:!1});for(const[f,i]of a.entries())try{if(i.error!==void 0){i.path!==void 0&&h.add(w.resolve(r,i.path)),o.push(L(i,i.error));continue}const c=await ut(i.path,r,t,n);h.add(c.file);const m=F(Number(c.identity.size),"Playwright attachment size"),g=Rt(m,l+m);if(g!==null){o.push(L(i,g));continue}const E=await Z(c.file,nt,"Playwright attachment",n,c.identity,p,`${f.toString().padStart(4,"0")}-${pt(i.name)}${ee(i)}`);l+=E.identity.sizeBytes,s.push({path:E.filePath,contentType:i.contentType,kind:"engine.playwright-attachment",name:i.name,summary:`${i.specTitle} retry ${i.retry.toString()}`,metadata:{...E.identity,specId:i.specId,specTitle:i.specTitle,retry:i.retry}})}catch(c){o.push(L(i,it(c)))}return{artifacts:s,failures:o,sourcePaths:h}}async function Jt(t,e,r,a,n){try{const s=await n.lstat(r);if(!s.isDirectory()||s.isSymbolicLink())throw new Error("Playwright output directory must be a non-symbolic-link directory");const o=await k.realpath(r);if(!D(t,o))throw new Error("Playwright output directory escaped the artifact root");const h=await Qt(o,a,n),l=[],p=w.join(e,"output");h.candidates.length>0&&await k.mkdir(p,{mode:448,recursive:!1});for(const[f,i]of h.candidates.entries()){const c=await Z(i.file,Y,"Playwright output file",n,i.identity,p,`${f.toString().padStart(5,"0")}-${pt(w.basename(i.file))}${ie(i.file)}`);l.push(I(c.filePath,c.identity,re(i.file),"engine.playwright-output",i.relativePath,"Playwright output"))}return{artifacts:l,bytes:h.bytes,entries:h.entries}}catch(s){if(gt(s))return{artifacts:[],bytes:0,entries:0};throw s}}async function Qt(t,e,r){const a=[];let n=0,s=0;const o=[{depth:0,directory:t}];for(;o.length>0;){const h=o.pop();if(h===void 0)continue;if(h.depth>at)throw new Error(`Playwright output exceeds the ${at.toString()} level directory depth limit`);const l=h.directory,p=await k.readdir(l,{withFileTypes:!0});p.sort((f,i)=>f.name.localeCompare(i.name));for(const f of p){if(s+=1,s>st)throw new X({bytes:n,entries:s,kind:"entries",limit:st,observed:s});const i=w.join(l,f.name),c=await r.lstat(i);if(c.isSymbolicLink())throw new Error("Playwright output must not contain symbolic links");if(c.isDirectory()){o.push({depth:h.depth+1,directory:i});continue}if(!c.isFile())continue;if(c.size<0n||c.size>BigInt(Number.MAX_SAFE_INTEGER))throw new Error("Playwright output file has an invalid size");const m=Number(c.size);if(n+=m,!Number.isSafeInteger(n)||n>Y)throw new X({bytes:n,entries:s,kind:"bytes",limit:Y,observed:n});e.has(i)||a.push({file:i,identity:c,relativePath:U(t,i)})}}return{bytes:n,candidates:a,entries:s}}function Zt(t){return t.kind==="bytes"?`Playwright output exceeds its ${t.limit.toString()} byte aggregate limit (observed ${t.observed.toString()} bytes)`:`Playwright output exceeds its ${t.limit.toString()} entry limit (observed ${t.observed.toString()} entries)`}async function ut(t,e,r,a){if(t===void 0)throw new Error("Playwright path attachment did not declare a path");const n=w.resolve(e,t),s=await a.lstat(n);if(!s.isFile()||s.isSymbolicLink())throw new Error("Playwright attachment path must be a regular non-symbolic-link file");const o=await k.realpath(n);if(!D(e,o)&&!D(r,o))throw new Error("Playwright attachment path must stay within the consumer or artifact root");return{file:o,identity:s}}function qt(t){const e=b(t,"Playwright report.stats");return{expected:F(e.expected,"Playwright stats.expected"),unexpected:F(e.unexpected,"Playwright stats.unexpected"),flaky:F(e.flaky,"Playwright stats.flaky"),skipped:F(e.skipped,"Playwright stats.skipped"),duration:q(e.duration,"Playwright stats.duration")}}function te(t){let e=Nt;const r=[];for(const a of t){if(e<=0)break;const s=Buffer.from(a.text,"utf8").subarray(0,e).toString("utf8");s.length>0&&r.push({...a,text:s}),e-=Buffer.byteLength(s)}return e<=0&&r.push({stream:"engine",text:"Playwright report log evidence was truncated at its bounded limit"}),r}function ee(t){if(t.path!==void 0){const e=w.extname(t.path);if(/^\.[a-z0-9]{1,12}$/iu.test(e))return e}return t.contentType==="application/json"?".json":t.contentType.startsWith("text/")?".txt":t.contentType==="image/png"?".png":t.contentType==="image/jpeg"?".jpg":".bin"}function re(t){const e=w.extname(t).toLowerCase();return e===".json"?"application/json":e===".zip"?"application/zip":e===".png"?"image/png":e===".jpg"||e===".jpeg"?"image/jpeg":e===".webm"?"video/webm":[".log",".txt",".md"].includes(e)?"text/plain; charset=utf-8":"application/octet-stream"}function ie(t){const e=w.extname(t).toLowerCase();return/^\.[a-z0-9]{1,12}$/u.test(e)?e:".bin"}function pt(t){const e=t.trim().toLowerCase().replace(/[^a-z0-9._-]+/gu,"-").replace(/^-+|-+$/gu,"").slice(0,80);return e.length>0?e:"attachment"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{_android as
|
|
1
|
+
import*as e from"@playwright/test";import{_android as t,_baseTest as r,_electron as o,chromium as a,defineConfig as s,devices as i,errors as m,expect as n,firefox as d,mergeExpects as l,mergeTests as f,request as p,selectors as x,test as c,webkit as w}from"@playwright/test";const{_utilityTest:g}=e;export{t as _android,r as _baseTest,o as _electron,g as _utilityTest,a as chromium,s as defineConfig,i as devices,m as errors,n as expect,d as firefox,l as mergeExpects,f as mergeTests,p as request,x as selectors,c as test,w as webkit};export{readPlaywrightContext}from"./runtime-input.js";export{defineTestkitConfig}from"./config.js";export{emulateCpu,emulateNetwork}from"./emulation.js";export{browserRequests}from"./browser-traffic.js";export{assertSafeUiTarget,assertSafeUiTargets,createAuthedApiClient,createSandboxId,createUiSandbox,resolveBackendBaseUrl,resolveFrontendBaseUrl,waitForAnimationFrames,waitForElementStable,waitForImagesLoaded,waitForNetworkIdle,waitForUiSettled}from"./sandbox.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{expect as
|
|
1
|
+
import{expect as E,request as R,test as x}from"@playwright/test";import{readPlaywrightContext as T}from"./runtime-input.js";const u=String;export function createUiSandbox(e={}){const t=createSandboxId(e);return{expect:E,test:x.extend({testkitSandbox:async({page:r},n)=>{await n({id:t,backendBaseUrl:resolveBackendBaseUrl(e),frontendBaseUrl:resolveFrontendBaseUrl(e),page:r})}})}}export function createSandboxId(e={}){const t=u(e.prefix||"ui").replace(/[^A-Za-z0-9_-]+/gu,"-").replace(/^-+|-+$/gu,"")||"ui",r=process.env.TEST_WORKER_INDEX||process.env.TESTKIT_WORKER_INDEX||"0",n=process.env.TESTKIT_LEASE_ID||process.env.TESTKIT_RUNTIME_ID||process.pid;return`${t}-${String(n)}-${r}`}export function resolveFrontendBaseUrl(e={}){const t=f();return m(U(e.frontendBaseUrl,e.baseUrl,l(t,"frontendBaseUrl"),process.env.TESTKIT_FRONTEND_BASE_URL,process.env.BASE_URL),{allowRemote:e.allowRemote??l(t,"allowRemote")})}export function resolveBackendBaseUrl(e={}){const t=f();return m(U(e.backendBaseUrl,l(t,"backendBaseUrl"),process.env.TESTKIT_BACKEND_BASE_URL,process.env.API_BASE_URL),{allowRemote:e.allowRemote??l(t,"allowRemote")})}export function assertSafeUiTarget(e,t={}){const r=p(e);if(!r)throw new Error(`Invalid UI target URL: ${u(e)}`);if(b(t))return r.toString().replace(/\/$/u,"");if(!I(r.hostname))throw new Error(`UI target must be local unless allowRemote is true: ${u(e)}`);return r.toString().replace(/\/$/u,"")}export function assertSafeUiTargets(e=[],t={}){return e.map(r=>assertSafeUiTarget(r,t))}export async function createAuthedApiClient(e=R,t={}){const r=assertSafeUiTarget(resolveBackendBaseUrl(t),t);return e.newContext({baseURL:r,extraHTTPHeaders:t.headers||{}})}export async function waitForUiSettled(e,t={}){const r=t.timeout??5e3;await e.waitForLoadState("domcontentloaded",{timeout:r}),await waitForAnimationFrames(e,t.frames??2),await waitForNetworkIdle(e,{frames:t.frames??2,timeout:r})}export async function waitForAnimationFrames(e,t=2){const r=Math.max(1,Number(t)||1);await e.evaluate(n=>new Promise(i=>{let o=n;const a=()=>{if(o-=1,o<=0){i();return}requestAnimationFrame(a)};requestAnimationFrame(a)}),r)}export async function waitForNetworkIdle(e,t={}){const r=t.timeout??2e3;try{await e.waitForLoadState("networkidle",{timeout:r})}catch{await e.waitForLoadState("load",{timeout:r}).catch(()=>{}),await waitForAnimationFrames(e,t.frames??2)}}export async function waitForElementStable(e,t,r={}){const n=Math.max(1,Number(r.frames)||2),i=e.locator(t);await i.waitFor({state:"visible",timeout:r.timeout}),await i.evaluate(async(o,a)=>{let s=0,c=JSON.stringify(o.getBoundingClientRect().toJSON());for(;s<a;){await new Promise(S=>requestAnimationFrame(()=>{S()}));const d=JSON.stringify(o.getBoundingClientRect().toJSON());d===c?s+=1:s=0,c=d}},n)}export async function waitForImagesLoaded(e,t=null){const r=t?`${t} img`:"img",n=e.locator(r),i=await n.count();for(let o=0;o<i;o+=1)await n.nth(o).evaluate(a=>{if(!(!(a instanceof HTMLImageElement)||a.complete))return new Promise(s=>{const c=()=>{s()};a.addEventListener("load",c,{once:!0}),a.addEventListener("error",c,{once:!0})})})}function m(e,t={}){const r=u(e||"").trim();if(!r)throw new Error("A UI base URL is required");return assertSafeUiTarget(r,t)}function f(){const e=T()?.uiConfig;return g(e)?e:{}}function p(e){try{return new URL(u(e))}catch{return null}}function b(e={}){if(w(e.allowRemote))return!0;const t=f();return w(l(t,"allowRemote"))}function w(e){return e===!0||e==="true"}function I(e){return e==="localhost"||e==="127.0.0.1"||e==="::1"||e.endsWith(".localhost")}function l(e,t){return g(e)?Reflect.get(e,t):void 0}function U(...e){return e.find(Boolean)}function g(e){return typeof e=="object"&&e!==null}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import c from"node:fs/promises";import P from"node:os";import
|
|
1
|
+
import c from"node:fs/promises";import P from"node:os";import 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": "
|
|
6
|
+
"packageLockSha256": "41f889dea57e892f03549d17c252c8cd601b637210d1c9fbb5ff095a4f34f506",
|
|
7
7
|
"esbuildVersion": "0.28.1",
|
|
8
8
|
"options": {
|
|
9
9
|
"format": "esm",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{spawn as f}from"node:child_process";const i=process.platform==="win32"?["SIGINT","SIGBREAK"]:["SIGINT","SIGTERM","SIGHUP"];export async function runProductCli(c,n){const e=f(process.execPath,[...process.execArgv,c,...n],{stdio:"inherit"}),r=o=>{process.platform!=="win32"&&e.kill(o)};for(const o of i)process.on(o,r);let s;try{s=await new Promise((o,t)=>{e.once("error",t),e.once("exit",(l,p)=>{o({code:l,signal:p})})})}finally{for(const o of i)process.off(o,r)}s.signal?process.kill(process.pid,s.signal):process.exitCode=s.code??1}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const TESTKIT_SUPPORTED_HOST_NODE_RANGE=">=22.15.0 <23.0.0 || >=24.14.1 <25.0.0",TESTKIT_SUPPORTED_HOST_NPM_RANGE=">=11.6.0 <12.0.0";const o=new Map([[22,Object.freeze([22,15,0])],[24,Object.freeze([24,14,1])]]);export function hostNodeIsSupported(r){const n=s(r);if(n===null)return!1;const e=o.get(n[0]);return e===void 0?!1:u(n,e)>=0}function s(r){const n=String(r).split(".");if(n.length!==3)return null;const e=n.map(t=>Number(t));return e.every(t=>Number.isSafeInteger(t)&&t>=0)?[e[0]??0,e[1]??0,e[2]??0]:null}function u(r,n){for(const e of[0,1,2]){if(r[e]>n[e])return 1;if(r[e]<n[e])return-1}return 0}
|
|
1
|
+
export const TESTKIT_SUPPORTED_HOST_NODE_RANGE=">=22.15.0 <23.0.0 || >=24.14.1 <25.0.0 || >=26.13.0 <27.0.0",TESTKIT_SUPPORTED_HOST_NPM_RANGE=">=11.6.0 <12.0.0";const o=new Map([[22,Object.freeze([22,15,0])],[24,Object.freeze([24,14,1])],[26,Object.freeze([26,13,0])]]);export function hostNodeIsSupported(r){const n=s(r);if(n===null)return!1;const e=o.get(n[0]);return e===void 0?!1:u(n,e)>=0}function s(r){const n=String(r).split(".");if(n.length!==3)return null;const e=n.map(t=>Number(t));return e.every(t=>Number.isSafeInteger(t)&&t>=0)?[e[0]??0,e[1]??0,e[2]??0]:null}function u(r,n){for(const e of[0,1,2]){if(r[e]>n[e])return 1;if(r[e]<n[e])return-1}return 0}
|
|
@@ -97,16 +97,10 @@ export interface UiStableLocator {
|
|
|
97
97
|
export interface UiStableElementPage {
|
|
98
98
|
locator(selector: string): UiStableLocator;
|
|
99
99
|
}
|
|
100
|
-
export interface ImageElement {
|
|
101
|
-
readonly complete: boolean;
|
|
102
|
-
addEventListener(type: "error" | "load", listener: () => void, options: {
|
|
103
|
-
once: true;
|
|
104
|
-
}): void;
|
|
105
|
-
}
|
|
106
100
|
export interface UiImageLocator {
|
|
107
101
|
count(): Promise<number>;
|
|
108
102
|
nth(index: number): {
|
|
109
|
-
evaluate(pageFunction: (element:
|
|
103
|
+
evaluate(pageFunction: (element: HTMLElement | SVGElement) => Promise<void> | undefined): Promise<void>;
|
|
110
104
|
};
|
|
111
105
|
}
|
|
112
106
|
export interface UiImagesPage {
|
|
@@ -139,6 +133,7 @@ export declare function waitForElementStable(page: UiStableElementPage, selector
|
|
|
139
133
|
timeout?: number;
|
|
140
134
|
}): Promise<void>;
|
|
141
135
|
export declare function waitForImagesLoaded(page: UiImagesPage, containerSelector?: string | null): Promise<void>;
|
|
136
|
+
export declare const _utilityTest: unknown;
|
|
142
137
|
export type * from "@playwright/test";
|
|
143
138
|
|
|
144
139
|
export {
|
package/docs/engines.md
CHANGED
|
@@ -271,7 +271,7 @@ timeouts, and process failures keep their precedence over both.
|
|
|
271
271
|
## Installation
|
|
272
272
|
|
|
273
273
|
Consumers declare one dependency, `@gfdelarue/testkit`. It installs Vitest
|
|
274
|
-
4.1.11 and Playwright 1.
|
|
274
|
+
4.1.11 and Playwright 1.64.1. Plain Vitest test files run with the product's own
|
|
275
275
|
Vitest 4.x and its config; see
|
|
276
276
|
[Plain Vitest test files](../README.md#plain-vitest-test-files). Otherwise a
|
|
277
277
|
product's copies of Vitest, Vite, esbuild, TypeScript, or Playwright don't
|
package/docs/release-notes.md
CHANGED
|
@@ -1,3 +1,61 @@
|
|
|
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
|
+
|
|
41
|
+
# 0.6.3
|
|
42
|
+
|
|
43
|
+
## A global Testkit runs the product's own install
|
|
44
|
+
|
|
45
|
+
When you start the CLI in a product that installs a different version of
|
|
46
|
+
`@gfdelarue/testkit`, the CLI now runs the product's install with the same
|
|
47
|
+
arguments. That covers a global install, a Homebrew, Scoop, AUR, or Chocolatey
|
|
48
|
+
package, and `npx @gfdelarue/testkit@latest`. Before, the CLI stopped with
|
|
49
|
+
`testkit_cli_version_mismatch` and printed the command to run the product's
|
|
50
|
+
install instead.
|
|
51
|
+
|
|
52
|
+
The CLI names the install it runs in one line on stderr, except in `--json`
|
|
53
|
+
mode. The exit code comes from the product's install. On Linux and macOS, a
|
|
54
|
+
stop signal sent to the CLI reaches the product's install, and the CLI ends
|
|
55
|
+
with the signal that ended it. A product that declares a version of
|
|
56
|
+
`@gfdelarue/testkit` but doesn't install one still gets
|
|
57
|
+
`testkit_cli_version_mismatch`.
|
|
58
|
+
|
|
1
59
|
# 0.6.2
|
|
2
60
|
|
|
3
61
|
## Static imports from `@gfdelarue/testkit/playwright` work in a CommonJS product
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gfdelarue/testkit",
|
|
3
|
-
"version": "0.6.
|
|
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,9 +96,9 @@
|
|
|
96
96
|
],
|
|
97
97
|
"devDependencies": {
|
|
98
98
|
"@eslint/js": "^10.0.1",
|
|
99
|
-
"@
|
|
100
|
-
"@homebridge/node-pty-prebuilt-multiarch": "^0.13.1",
|
|
99
|
+
"@homebridge/node-pty-prebuilt-multiarch": "^0.15.0",
|
|
101
100
|
"@kitschpatrol/tokei": "2.0.2",
|
|
101
|
+
"@moontern-labs/code-budget": "0.1.3",
|
|
102
102
|
"@types/picomatch": "4.0.3",
|
|
103
103
|
"@types/yauzl": "3.4.0",
|
|
104
104
|
"dts-bundle-generator": "9.5.1",
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
"verdaccio": "6.10.3"
|
|
114
114
|
},
|
|
115
115
|
"dependencies": {
|
|
116
|
-
"@playwright/test": "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.
|
|
128
|
-
"playwright-core": "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": {
|