momentic 1.0.66 → 1.0.67
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/cli.js +2 -2
- package/package.json +1 -1
- package/static/assets/{index-Hvw259GG.js → index-Y-A10OWz.js} +260 -260
- package/static/index.html +1 -1
package/bin/cli.js
CHANGED
|
@@ -2926,7 +2926,7 @@ ${i.stack}`),a.status(500).send("Internal Server Error")}),n){let i=By.static(n)
|
|
|
2926
2926
|
|
|
2927
2927
|
Using Command Prompt on Windows:
|
|
2928
2928
|
for /f "tokens=5" %a in ('netstat -ano ^| findstr :58888') do taskkill /PID %a /F
|
|
2929
|
-
`)}import RP from"events";import $b from"fs";import IP from"open";import Wa from"path";import{fileURLToPath as PP}from"url";import zI from"diff-lines";import{gt as BI}from"semver";import{execSync as xI}from"child_process";import{platform as TI}from"os";function Cd(){return Wy()?(x.info("Setting device pixel ratio to 2 automatically since a Mac OS Retina screen was detected."),x.info("If you are using a low pixel-density monitor, you should manually set --pixel-ratio to 1 to avoid incorrect viewport calculations."),x.info("Confirm your device's pixel-ratio at https://www.mydevice.io."),2):(x.info("Setting device pixel ratio to 1."),x.info("If you are using Momentic on a high-pixel density (HiDPI) monitor, relaunch with the --pixel-ratio option to avoid incorrect viewport calculations"),x.info("Confirm your device's pixel-ratio at https://www.mydevice.io."),1)}function Wy(){return TI()==="darwin"&&xI("system_profiler SPDisplaysDataType").toString().includes("Retina")}function Ad(n){Wy()&&n===1&&(x.warn("If you are using Momentic on a Retina screen, relaunch with the --pixel-ratio option to avoid incorrect viewport calculations."),x.warn("Confirm your device's pixel-ratio at https://www.mydevice.io."))}import EI from"@actions/exec";import CI from"@actions/io";import AI from"quote";import RI from"string-argv";async function Gy(n,e=!0){let t=RI(n),r=await CI.which(t[0],!0),o=t.slice(1),i=EI.exec(AI(r),o,{delay:100});if(e)return i}import{existsSync as II,statSync as PI}from"fs";var Rd=!!process.env.CI||!process.stdout.isTTY;function ki(n){try{return II(n)&&PI(n).isDirectory()}catch(e){return x.error({err:e},`Error reading path ${n} during directory existence check`),!1}}import OI from"csv-parser";import{createReadStream as LI}from"fs";function Id(n){return new Promise((e,t)=>{let r=[];LI(n).pipe(OI()).on("data",o=>r.push(o)).on("end",()=>e(r)).on("error",o=>t(o))})}import Oa from"semver";import{z as La}from"zod";var Ni="1.0.66",kI="https://registry.npmjs.org/momentic",NI=La.object({versions:La.record(La.string(),La.unknown()).optional()});async function Vy(n){if(!Ni){n.warn("Unable to check CLI version because CLI_VERSION is not set");return}let e;for(let r=0;r<2;r++)try{let o=await U(fetch(kI),{milliseconds:2e3});if(!o.ok)throw new Error(`Got error status code ${o.statusText}`);let i=await o.json();e=NI.parse(i).versions;break}catch(o){n.warn({err:o},"Failed to fetch npm registry data")}if(!e){n.warn("Failed to fetch npm registry data. Skipping version check.");return}let t;for(let r of Object.keys(e))Oa.valid(r)&&(!t||Oa.gt(r,t))&&Oa.gt(r,Ni)&&Oa.lt(r,"2.0.0")&&!r.includes("alpha")&&(t=r);t&&(x.warn(`Update available: v${Ni} -> v${t}`),x.warn("This version may be missing critical fixes, features, and security updates."),x.warn(`Run "npx momentic@${t} -V" to update`))}import{existsSync as _I,mkdirSync as DI,statSync as FI}from"fs";import{dirname as UI}from"path";import Ky from"readline/promises";import{hostname as MI}from"os";var H=Ea({app:"cli",clientToken:"pub7eb923f18fb3f1d42ac5eba8c5ea13a5",hostname:MI(),disableConsoleLogs:!0}).child({version:"1.0.66"});var Pd=!1,Yy=(()=>{try{return FI("/.dockerenv"),!0}catch{return!1}})();async function Xe(n,e){if(Rd||Pd||Yy)return!0;H.flush(),await new Promise(s=>setTimeout(s,500));let t=Ky.createInterface({input:process.stdin,output:process.stdout}),r=n.split("."),o;if(r.length===1)o=n;else{let s=`${r.slice(0,r.length-1).join(".").trim()}.`;e?x.warn(s):x.log(s),o=r[r.length-1].trim()}let i=await t.question(`${o} ('y' for yes / n for no / 'A' to accept all) `);return t.close(),i==="A"?(Pd=!0,setTimeout(()=>{Pd=!1},3e3),!0):i.toLowerCase()==="y"}async function Od(n){let e=UI(n);return ki(e)?_I(n)?Xe(`File '${qy(n)}' already exists. Overwrite existing content?`,!0):!0:await Xe(`Directory '${qy(e)}' doesn't exist. Create it now?`,!0)?(DI(e,{recursive:!0}),!0):!1}function qy(n){return n.replace(/(\s+)/g,"\\$1")}async function Xy(n,e){if(Rd||Yy)return e;let t=Ky.createInterface({input:process.stdin,output:process.stdout}),r=await t.question(`${n} `);return t.close(),r.trim()||e}async function Jy({test:n,fragment:e,entities:t,client:r,logger:o,yes:i}){BI(e.schemaVersion,st)&&(x.error(`This version of the CLI does not support the schema version of the fragment (${e.schemaVersion}). Please update to the latest version of the CLI and retry this command.`),process.exit(1)),Im(e.steps).forEach(h=>{t.modules[h]||(x.error(`The test patch contains a module with id ${h} that could not be found in the current project. The patch may be out of date, or you may need to pull the module locally before applying.`),process.exit(1))}),e.createdAt.getTime()<Date.now()-7*24*60*60*1e3&&!i&&!await Xe("The test patch you are applying is more than 7 days old. Are you sure you want to continue?",!0)&&process.exit(1);let a=n.lastModified.getTime();e.createdAt.getTime()+60*60*1e3<a&&!i&&!await Xe("The test patch you are applying was created before the test was last updated. Are you sure you want to continue?",!0)&&process.exit(1);let l,c;if(e.schemaVersion!==st){let{steps:h,newVersion:f}=await ei({metadata:{id:e.id,schemaVersion:e.schemaVersion},steps:e.steps,logger:x});l=f,c=ve.array().parse(h)}else c=ve.array().parse(e.steps);let{stepsToSave:p,moduleUpdates:d}=await Et({steps:c}),m=jc(n.fullFilePath,o,t).steps;if(!Array.isArray(m))throw new Error(`Test ${n.fullFilePath} is missing steps array`);let u=zI(JSON.stringify(m,void 0,2),JSON.stringify(p,void 0,2),{n_surrounding:5});x.dimmed("=".repeat(30)),x.dimmed(u),x.dimmed("=".repeat(30)),x.dimmed(""),l&&x.warn(`If this patch is applied, your test will also be automatically upgraded to the latest schema version (${l}). Schema upgrades have no impact on functionality, although you may notice minor differences in the test YAML.`),!i&&!await Xe("Do you want to apply this patch?")&&(x.dimmed("Cancelled."),process.exit(1)),pi(n.relativePath,p,l??e.schemaVersion,t.project),x.success("Patch applied successfully."),await r.patchTestFragment(e.id,{applied:!0,appliedAt:new Date})}import{cloneDeep as jI}from"lodash-es";async function ka({client:n,skipPrompts:e,project:t}){let r=await n.getAllEnvironments(),o=jI(t.config);o.environments||(o.environments=[]);for(let i of r){let s=o.environments?.find(a=>a.name===i.name);if(s)!e&&!await Xe(`Environment ${i.name} already exists in the project configuration file. Would you like to overwrite its variables?`)&&process.exit(1),s.baseUrl=i.variables[be],delete i.variables[be],s.envVariables=i.variables;else{let a=i.variables[be];delete i.variables[be],o.environments.push({name:i.name,baseUrl:a,envVariables:i.variables})}}mo(o,t.configFilePath),x.success(`Pulled ${r.length} environments successfully! Please make sure to commit any changes to your project configuration file.`)}import{createHash as Zy}from"crypto";import Qy from"fs";async function tb({testsToFetch:n,client:e,all:t,yes:r}){let{tests:o,modules:i}=await e.getTestYAMLExport({paths:n,all:t}),s=0;for(let[l,c]of Object.entries(o)){let p=eb(l,Se.TEST);!r&&!await Od(p)||(s+=1,Qy.writeFileSync(p,c,"utf-8"),H.info({checksum:Zy("md5").update(c).digest("hex")},`Wrote '${p}'`))}let a=0;for(let[l,c]of Object.entries(i)){let p=eb(l,Se.MODULE);!r&&!await Od(p)||(a+=1,Qy.writeFileSync(p,c,"utf-8"),H.info({checksum:Zy("md5").update(c).digest("hex")},`Wrote '${p}'`))}s===0?x.success("Pulled 0 tests."):x.success(`Pulled ${s} test${s>1?"s":""}${a?` and ${a} module${a>1?"s":""}`:""}!`)}function eb(n,e){switch(e){case Se.TEST:return`${Pe(n)}.${gn.TEST}`;case Se.MODULE:return`${Pe(n)}.${gn.MODULE}`;default:throw new Error(`Unknown entity type ${e}`)}}async function nb(n){let{project:e,client:t,skipPrompts:r}=n;x.info("Welcome to the Momentic Cloud importer wizard! \u{1F636}\u200D\u{1F32B}\uFE0F"),x.info("Importing environments from Momentic Cloud."),x.info(`This command will overwrite all local environment configuration in ${e.configFilePath} with environments from Momentic Cloud.`),await Xe("Are you sure you want to proceed?",!0)||(x.info("Aborting..."),process.exit(1)),await ka({client:t,project:e,skipPrompts:r}),x.success(`Successfully imported environments from Momentic Cloud. We recommend pulling secrets out of ${e.configFilePath} into .env files or dynamically set environment variables for security.`),x.info("Importing tests and modules from Momentic Cloud."),await tb({testsToFetch:[],client:t,all:!0,yes:r}),x.success("Successfully imported tests and modules from Momentic Cloud. You can move them to the desired location in your project. We recommend committing these files to version control so you can share them with team members and run Momentic tests in CI.")}import Rt from"fs";import Sr from"path";import $I from"yaml";function rb(){ki("momentic")||(x.error(`The migration command should be ran from the v0 root Momentic directory, which should contain a folder called 'momentic'. No folder named 'momentic' was found in the current working directory (${process.cwd()}).`),process.exit(1));let n={name:"default",include:Pi,environments:[]};x.info("Migrating environments");let e=ob(["momentic/environments"],HI);for(let r of e){let o=$I.parse(Rt.readFileSync(r,"utf-8"));try{let i=Yi.parse(o),s=i.variables[be]??"";delete i.variables[be],n.environments?.push({name:i.name,baseUrl:s,envVariables:i.variables})}catch(i){x.error(`${r} failed to parse as a valid environment file.`),x.error(i),process.exit(1)}}x.info("Migrating tests");let t=Bc("./momentic",x);for(let r of t){let o=Sr.join(...r.fullPathSegments),i=Sr.join(Sr.dirname(o),`${r.fileName.slice(0,-5)}.test.yaml`);x.info(`Moving test ${o} to ${i}`),Rt.renameSync(o,i)}if(ki("momentic/modules")){x.info("Migrating modules");for(let r of Rt.readdirSync("./momentic/modules")){if(!r.endsWith(".yaml"))continue;let o=Sr.resolve(Sr.join("./momentic/modules",r));if(!Rt.readFileSync(o,"utf-8").includes("schemaVersion")){x.warn(`Skipping file ${o} since it does not have valid Momentic module contents`);continue}let s=`${o.slice(0,-5)}.module.yaml`;x.info(`Moving module ${o} to ${s}`),Rt.renameSync(o,s)}}return x.info("Writing new project configuration file"),mo(n,"momentic.config.yaml"),Rt.rmSync("./momentic/environments",{recursive:!0,force:!0}),Rt.rmSync("./momentic/fixtures",{recursive:!0,force:!0}),x.success("Migration succeeded!"),x.info("Going forward:"),x.info(` - You can store test and module files anywhere under the project root (${process.cwd()})`),x.info(" - Environment details and other common options are tracked in the root momentic.config.yaml file"),n}function ob(n,e,t=new Set){for(let r of n){let o=Sr.resolve(r),i=!1;try{i=Rt.existsSync(o)&&Rt.statSync(o).isDirectory()}catch(s){x.error({err:s},`Error reading path ${o} during collect paths`)}if(o&&i){let s=Rt.readdirSync(o).map(a=>Sr.join(o,a));ob(s,e,t);continue}if(o.endsWith(".yaml")){try{if(!Rt.existsSync(o)||!Rt.statSync(o).isFile()){x.warn(`File not found or unreadable: ${o}`);continue}}catch(s){x.error({err:s},`Error reading file ${o} during collect paths`);continue}if(!e(o))continue;t.add(o)}}return t}function HI(n){return n.endsWith(".yaml")?Rt.readFileSync(n,"utf8").includes("momentic/environment")?!0:(x.warn(`Skipping YAML that is not a Momentic environment: ${n}`),!1):!1}import{Argument as Na,Option as ke}from"@commander-js/extra-typings";import{validateHeaderValue as WI}from"http";import{cpus as ib}from"os";import{parse as GI}from"yaml";import{z as B}from"zod";var Ma=58888,kd=30*60*1e3,$n=new ke("--api-key <key>","API key for authentication. If not supplied, attempts to read the MOMENTIC_API_KEY env var."),vr=new ke("--server <server>","Momentic server to use. Leave unchanged unless using Momentic on-premise."),Hn=new ke("-y, --yes","Skip all confirmation prompts."),Nd=new ke("-w, --wait","Wait for tests to finish running before exiting. Only applicable when running tests remotely").implies({remote:!0}),Md=new ke("--wait-timeout <waitTimeout>",`The maximum number of seconds to wait for tests to complete. Only applicable when the --wait option is specified. Defaults to ${kd/1e3} seconds.`),_a=new ke("--custom-headers <customHeaders...>","Specify custom headers in the form HEADER=VALUE to be sent with each request during the test. Multiple entries can be provided."),_d=new ke("--no-report","Skip reporting test results to Momentic Cloud when running with the --local flag.").implies({local:!0}),sb=new ke("--reporter <reporter>","Output report files in a standardized format to a local directory. See the --reporter-dir flag for information on the output directory.").choices(Object.values(ps)),ab=new ke("--reporter-dir <reporterDir>","Output directory to store report files. Relative paths are resolved relative to the project root, which is defined by the detected momentic.config.yaml. Defaults to 'reports' if unset."),lb=new ke("--include <includePatterns...>","Only include tests that match the provided regex patterns. Multiple patterns can be provided. The patterns will be matched against the test file paths and the pattern only needs to match a part of the path for the test to be included."),cb=new ke("--exclude <excludePatterns...>","The inverted version of --include: a test that matches any of the provided exclusion patterns will be excluded from running."),Dd=new ke("--pixel-ratio <pixelRatio>","Device pixel ratio of your screen or monitor. Mac OS Retina displays and machines marketed as 'HiDPI' should set this to 2. Visit https://www.mydevice.io/ to find your device's pixel ratio."),db=new ke("--port <port>",`Port to run the app on. Defaults to ${Ma}.`),Fd=new ke("--input-csv <inputCsv>","Path to a CSV file on disk where each row represents a set of inputs that will be made available to all tests that are ran. The first line of the CSV must be the input names."),Da=new ke("--env <env>","Name of the environment to use when running tests."),Fa=new ke("--url-override <urlOverride>","Fully qualified url (e.g. https://www.google.com) to start all tests from. Overrides any default starting url set from the test or environment."),pb=new ke("--shard-index <shardIndex>","The index of the shard to run tests for. Defaults to 1.").default(1).argParser(n=>parseInt(n,10)),wo=new ke("-c, --config <configPath>","Absolute or relative path to a Momentic configuration file (*.momentic.config.yaml)"),Ud=new ke("-f, --filter <filter>","Run tests within the project that has a name equal to the filter provided. This option cannot be used together with file path or directory arguments, but substring matches are allowed."),ub=new ke("--shard-count <shardCount>","The number of shards that tests are being run on. Defaults to 1.").default(1).argParser(n=>parseInt(n,10)),mb=new Na("<tests...>",`One or more test paths to queue on Momentic Cloud.
|
|
2929
|
+
`)}import RP from"events";import $b from"fs";import IP from"open";import Wa from"path";import{fileURLToPath as PP}from"url";import zI from"diff-lines";import{gt as BI}from"semver";import{execSync as xI}from"child_process";import{platform as TI}from"os";function Cd(){return Wy()?(x.info("Setting device pixel ratio to 2 automatically since a Mac OS Retina screen was detected."),x.info("If you are using a low pixel-density monitor, you should manually set --pixel-ratio to 1 to avoid incorrect viewport calculations."),x.info("Confirm your device's pixel-ratio at https://www.mydevice.io."),2):(x.info("Setting device pixel ratio to 1."),x.info("If you are using Momentic on a high-pixel density (HiDPI) monitor, relaunch with the --pixel-ratio option to avoid incorrect viewport calculations"),x.info("Confirm your device's pixel-ratio at https://www.mydevice.io."),1)}function Wy(){return TI()==="darwin"&&xI("system_profiler SPDisplaysDataType").toString().includes("Retina")}function Ad(n){Wy()&&n===1&&(x.warn("If you are using Momentic on a Retina screen, relaunch with the --pixel-ratio option to avoid incorrect viewport calculations."),x.warn("Confirm your device's pixel-ratio at https://www.mydevice.io."))}import EI from"@actions/exec";import CI from"@actions/io";import AI from"quote";import RI from"string-argv";async function Gy(n,e=!0){let t=RI(n),r=await CI.which(t[0],!0),o=t.slice(1),i=EI.exec(AI(r),o,{delay:100});if(e)return i}import{existsSync as II,statSync as PI}from"fs";var Rd=!!process.env.CI||!process.stdout.isTTY;function ki(n){try{return II(n)&&PI(n).isDirectory()}catch(e){return x.error({err:e},`Error reading path ${n} during directory existence check`),!1}}import OI from"csv-parser";import{createReadStream as LI}from"fs";function Id(n){return new Promise((e,t)=>{let r=[];LI(n).pipe(OI()).on("data",o=>r.push(o)).on("end",()=>e(r)).on("error",o=>t(o))})}import Oa from"semver";import{z as La}from"zod";var Ni="1.0.67",kI="https://registry.npmjs.org/momentic",NI=La.object({versions:La.record(La.string(),La.unknown()).optional()});async function Vy(n){if(!Ni){n.warn("Unable to check CLI version because CLI_VERSION is not set");return}let e;for(let r=0;r<2;r++)try{let o=await U(fetch(kI),{milliseconds:2e3});if(!o.ok)throw new Error(`Got error status code ${o.statusText}`);let i=await o.json();e=NI.parse(i).versions;break}catch(o){n.warn({err:o},"Failed to fetch npm registry data")}if(!e){n.warn("Failed to fetch npm registry data. Skipping version check.");return}let t;for(let r of Object.keys(e))Oa.valid(r)&&(!t||Oa.gt(r,t))&&Oa.gt(r,Ni)&&Oa.lt(r,"2.0.0")&&!r.includes("alpha")&&(t=r);t&&(x.warn(`Update available: v${Ni} -> v${t}`),x.warn("This version may be missing critical fixes, features, and security updates."),x.warn(`Run "npx momentic@${t} -V" to update`))}import{existsSync as _I,mkdirSync as DI,statSync as FI}from"fs";import{dirname as UI}from"path";import Ky from"readline/promises";import{hostname as MI}from"os";var H=Ea({app:"cli",clientToken:"pub7eb923f18fb3f1d42ac5eba8c5ea13a5",hostname:MI(),disableConsoleLogs:!0}).child({version:"1.0.67"});var Pd=!1,Yy=(()=>{try{return FI("/.dockerenv"),!0}catch{return!1}})();async function Xe(n,e){if(Rd||Pd||Yy)return!0;H.flush(),await new Promise(s=>setTimeout(s,500));let t=Ky.createInterface({input:process.stdin,output:process.stdout}),r=n.split("."),o;if(r.length===1)o=n;else{let s=`${r.slice(0,r.length-1).join(".").trim()}.`;e?x.warn(s):x.log(s),o=r[r.length-1].trim()}let i=await t.question(`${o} ('y' for yes / n for no / 'A' to accept all) `);return t.close(),i==="A"?(Pd=!0,setTimeout(()=>{Pd=!1},3e3),!0):i.toLowerCase()==="y"}async function Od(n){let e=UI(n);return ki(e)?_I(n)?Xe(`File '${qy(n)}' already exists. Overwrite existing content?`,!0):!0:await Xe(`Directory '${qy(e)}' doesn't exist. Create it now?`,!0)?(DI(e,{recursive:!0}),!0):!1}function qy(n){return n.replace(/(\s+)/g,"\\$1")}async function Xy(n,e){if(Rd||Yy)return e;let t=Ky.createInterface({input:process.stdin,output:process.stdout}),r=await t.question(`${n} `);return t.close(),r.trim()||e}async function Jy({test:n,fragment:e,entities:t,client:r,logger:o,yes:i}){BI(e.schemaVersion,st)&&(x.error(`This version of the CLI does not support the schema version of the fragment (${e.schemaVersion}). Please update to the latest version of the CLI and retry this command.`),process.exit(1)),Im(e.steps).forEach(h=>{t.modules[h]||(x.error(`The test patch contains a module with id ${h} that could not be found in the current project. The patch may be out of date, or you may need to pull the module locally before applying.`),process.exit(1))}),e.createdAt.getTime()<Date.now()-7*24*60*60*1e3&&!i&&!await Xe("The test patch you are applying is more than 7 days old. Are you sure you want to continue?",!0)&&process.exit(1);let a=n.lastModified.getTime();e.createdAt.getTime()+60*60*1e3<a&&!i&&!await Xe("The test patch you are applying was created before the test was last updated. Are you sure you want to continue?",!0)&&process.exit(1);let l,c;if(e.schemaVersion!==st){let{steps:h,newVersion:f}=await ei({metadata:{id:e.id,schemaVersion:e.schemaVersion},steps:e.steps,logger:x});l=f,c=ve.array().parse(h)}else c=ve.array().parse(e.steps);let{stepsToSave:p,moduleUpdates:d}=await Et({steps:c}),m=jc(n.fullFilePath,o,t).steps;if(!Array.isArray(m))throw new Error(`Test ${n.fullFilePath} is missing steps array`);let u=zI(JSON.stringify(m,void 0,2),JSON.stringify(p,void 0,2),{n_surrounding:5});x.dimmed("=".repeat(30)),x.dimmed(u),x.dimmed("=".repeat(30)),x.dimmed(""),l&&x.warn(`If this patch is applied, your test will also be automatically upgraded to the latest schema version (${l}). Schema upgrades have no impact on functionality, although you may notice minor differences in the test YAML.`),!i&&!await Xe("Do you want to apply this patch?")&&(x.dimmed("Cancelled."),process.exit(1)),pi(n.relativePath,p,l??e.schemaVersion,t.project),x.success("Patch applied successfully."),await r.patchTestFragment(e.id,{applied:!0,appliedAt:new Date})}import{cloneDeep as jI}from"lodash-es";async function ka({client:n,skipPrompts:e,project:t}){let r=await n.getAllEnvironments(),o=jI(t.config);o.environments||(o.environments=[]);for(let i of r){let s=o.environments?.find(a=>a.name===i.name);if(s)!e&&!await Xe(`Environment ${i.name} already exists in the project configuration file. Would you like to overwrite its variables?`)&&process.exit(1),s.baseUrl=i.variables[be],delete i.variables[be],s.envVariables=i.variables;else{let a=i.variables[be];delete i.variables[be],o.environments.push({name:i.name,baseUrl:a,envVariables:i.variables})}}mo(o,t.configFilePath),x.success(`Pulled ${r.length} environments successfully! Please make sure to commit any changes to your project configuration file.`)}import{createHash as Zy}from"crypto";import Qy from"fs";async function tb({testsToFetch:n,client:e,all:t,yes:r}){let{tests:o,modules:i}=await e.getTestYAMLExport({paths:n,all:t}),s=0;for(let[l,c]of Object.entries(o)){let p=eb(l,Se.TEST);!r&&!await Od(p)||(s+=1,Qy.writeFileSync(p,c,"utf-8"),H.info({checksum:Zy("md5").update(c).digest("hex")},`Wrote '${p}'`))}let a=0;for(let[l,c]of Object.entries(i)){let p=eb(l,Se.MODULE);!r&&!await Od(p)||(a+=1,Qy.writeFileSync(p,c,"utf-8"),H.info({checksum:Zy("md5").update(c).digest("hex")},`Wrote '${p}'`))}s===0?x.success("Pulled 0 tests."):x.success(`Pulled ${s} test${s>1?"s":""}${a?` and ${a} module${a>1?"s":""}`:""}!`)}function eb(n,e){switch(e){case Se.TEST:return`${Pe(n)}.${gn.TEST}`;case Se.MODULE:return`${Pe(n)}.${gn.MODULE}`;default:throw new Error(`Unknown entity type ${e}`)}}async function nb(n){let{project:e,client:t,skipPrompts:r}=n;x.info("Welcome to the Momentic Cloud importer wizard! \u{1F636}\u200D\u{1F32B}\uFE0F"),x.info("Importing environments from Momentic Cloud."),x.info(`This command will overwrite all local environment configuration in ${e.configFilePath} with environments from Momentic Cloud.`),await Xe("Are you sure you want to proceed?",!0)||(x.info("Aborting..."),process.exit(1)),await ka({client:t,project:e,skipPrompts:r}),x.success(`Successfully imported environments from Momentic Cloud. We recommend pulling secrets out of ${e.configFilePath} into .env files or dynamically set environment variables for security.`),x.info("Importing tests and modules from Momentic Cloud."),await tb({testsToFetch:[],client:t,all:!0,yes:r}),x.success("Successfully imported tests and modules from Momentic Cloud. You can move them to the desired location in your project. We recommend committing these files to version control so you can share them with team members and run Momentic tests in CI.")}import Rt from"fs";import Sr from"path";import $I from"yaml";function rb(){ki("momentic")||(x.error(`The migration command should be ran from the v0 root Momentic directory, which should contain a folder called 'momentic'. No folder named 'momentic' was found in the current working directory (${process.cwd()}).`),process.exit(1));let n={name:"default",include:Pi,environments:[]};x.info("Migrating environments");let e=ob(["momentic/environments"],HI);for(let r of e){let o=$I.parse(Rt.readFileSync(r,"utf-8"));try{let i=Yi.parse(o),s=i.variables[be]??"";delete i.variables[be],n.environments?.push({name:i.name,baseUrl:s,envVariables:i.variables})}catch(i){x.error(`${r} failed to parse as a valid environment file.`),x.error(i),process.exit(1)}}x.info("Migrating tests");let t=Bc("./momentic",x);for(let r of t){let o=Sr.join(...r.fullPathSegments),i=Sr.join(Sr.dirname(o),`${r.fileName.slice(0,-5)}.test.yaml`);x.info(`Moving test ${o} to ${i}`),Rt.renameSync(o,i)}if(ki("momentic/modules")){x.info("Migrating modules");for(let r of Rt.readdirSync("./momentic/modules")){if(!r.endsWith(".yaml"))continue;let o=Sr.resolve(Sr.join("./momentic/modules",r));if(!Rt.readFileSync(o,"utf-8").includes("schemaVersion")){x.warn(`Skipping file ${o} since it does not have valid Momentic module contents`);continue}let s=`${o.slice(0,-5)}.module.yaml`;x.info(`Moving module ${o} to ${s}`),Rt.renameSync(o,s)}}return x.info("Writing new project configuration file"),mo(n,"momentic.config.yaml"),Rt.rmSync("./momentic/environments",{recursive:!0,force:!0}),Rt.rmSync("./momentic/fixtures",{recursive:!0,force:!0}),x.success("Migration succeeded!"),x.info("Going forward:"),x.info(` - You can store test and module files anywhere under the project root (${process.cwd()})`),x.info(" - Environment details and other common options are tracked in the root momentic.config.yaml file"),n}function ob(n,e,t=new Set){for(let r of n){let o=Sr.resolve(r),i=!1;try{i=Rt.existsSync(o)&&Rt.statSync(o).isDirectory()}catch(s){x.error({err:s},`Error reading path ${o} during collect paths`)}if(o&&i){let s=Rt.readdirSync(o).map(a=>Sr.join(o,a));ob(s,e,t);continue}if(o.endsWith(".yaml")){try{if(!Rt.existsSync(o)||!Rt.statSync(o).isFile()){x.warn(`File not found or unreadable: ${o}`);continue}}catch(s){x.error({err:s},`Error reading file ${o} during collect paths`);continue}if(!e(o))continue;t.add(o)}}return t}function HI(n){return n.endsWith(".yaml")?Rt.readFileSync(n,"utf8").includes("momentic/environment")?!0:(x.warn(`Skipping YAML that is not a Momentic environment: ${n}`),!1):!1}import{Argument as Na,Option as ke}from"@commander-js/extra-typings";import{validateHeaderValue as WI}from"http";import{cpus as ib}from"os";import{parse as GI}from"yaml";import{z as B}from"zod";var Ma=58888,kd=30*60*1e3,$n=new ke("--api-key <key>","API key for authentication. If not supplied, attempts to read the MOMENTIC_API_KEY env var."),vr=new ke("--server <server>","Momentic server to use. Leave unchanged unless using Momentic on-premise."),Hn=new ke("-y, --yes","Skip all confirmation prompts."),Nd=new ke("-w, --wait","Wait for tests to finish running before exiting. Only applicable when running tests remotely").implies({remote:!0}),Md=new ke("--wait-timeout <waitTimeout>",`The maximum number of seconds to wait for tests to complete. Only applicable when the --wait option is specified. Defaults to ${kd/1e3} seconds.`),_a=new ke("--custom-headers <customHeaders...>","Specify custom headers in the form HEADER=VALUE to be sent with each request during the test. Multiple entries can be provided."),_d=new ke("--no-report","Skip reporting test results to Momentic Cloud when running with the --local flag.").implies({local:!0}),sb=new ke("--reporter <reporter>","Output report files in a standardized format to a local directory. See the --reporter-dir flag for information on the output directory.").choices(Object.values(ps)),ab=new ke("--reporter-dir <reporterDir>","Output directory to store report files. Relative paths are resolved relative to the project root, which is defined by the detected momentic.config.yaml. Defaults to 'reports' if unset."),lb=new ke("--include <includePatterns...>","Only include tests that match the provided regex patterns. Multiple patterns can be provided. The patterns will be matched against the test file paths and the pattern only needs to match a part of the path for the test to be included."),cb=new ke("--exclude <excludePatterns...>","The inverted version of --include: a test that matches any of the provided exclusion patterns will be excluded from running."),Dd=new ke("--pixel-ratio <pixelRatio>","Device pixel ratio of your screen or monitor. Mac OS Retina displays and machines marketed as 'HiDPI' should set this to 2. Visit https://www.mydevice.io/ to find your device's pixel ratio."),db=new ke("--port <port>",`Port to run the app on. Defaults to ${Ma}.`),Fd=new ke("--input-csv <inputCsv>","Path to a CSV file on disk where each row represents a set of inputs that will be made available to all tests that are ran. The first line of the CSV must be the input names."),Da=new ke("--env <env>","Name of the environment to use when running tests."),Fa=new ke("--url-override <urlOverride>","Fully qualified url (e.g. https://www.google.com) to start all tests from. Overrides any default starting url set from the test or environment."),pb=new ke("--shard-index <shardIndex>","The index of the shard to run tests for. Defaults to 1.").default(1).argParser(n=>parseInt(n,10)),wo=new ke("-c, --config <configPath>","Absolute or relative path to a Momentic configuration file (*.momentic.config.yaml)"),Ud=new ke("-f, --filter <filter>","Run tests within the project that has a name equal to the filter provided. This option cannot be used together with file path or directory arguments, but substring matches are allowed."),ub=new ke("--shard-count <shardCount>","The number of shards that tests are being run on. Defaults to 1.").default(1).argParser(n=>parseInt(n,10)),mb=new Na("<tests...>",`One or more test paths to queue on Momentic Cloud.
|
|
2930
2930
|
|
|
2931
2931
|
A test path is a lowercased version of your test name where spaces are replaced with dashes: 'npx momentic@latest pull hello-world'.`),hb=new Na("<tests...>","One or more test file path or folders that exist on the local machine: 'npx momentic@latest run hello-world.test.yaml'.").argOptional(),fb=new Na("<suites...>",`One or more suite paths that exist on Momentic Cloud.
|
|
2932
2932
|
|
|
@@ -2941,6 +2941,6 @@ ${n.map(d=>` - ${d}`).join(`
|
|
|
2941
2941
|
${n.map(d=>` - ${d}`).join(`
|
|
2942
2942
|
`)}`),Object.values(e.tests).forEach(d=>{n.some(m=>d.relativePath.includes(m))&&a.add(d.fullFilePath)}))}else{!r&&!await Xe("No test paths or substrings were provided. Do you want to run all tests?")&&(x.error("Cancelled by user."),process.exit(1));let c=Object.values(e.tests);x.info(`Reading all ${c.length} tests in the project from local disk.`),c.forEach(p=>{a.add(p.fullFilePath)})}for(let c of a){let p=Vd.relative(t.rootDir,c);o&&!o.some(d=>new RegExp(d).test(p))&&a.delete(c),i&&i.some(d=>new RegExp(d).test(p))&&a.delete(c)}let l=Array.from(a).map(async c=>{try{let p=await oa(c,H,e);if(hP.gt(p.schemaVersion,st)&&x.warn(`Test ${c} has schema version ${p.schemaVersion}, which is greater than what is currently supported by this SDK. Please update your momentic package version to avoid unexpected behavior.`),p.disabled)return null;if(s&&s.length>0){let d=p.labels||[];if(!s.some(u=>d.includes(u)))return null}return{...p,fullFilePath:c,relativeFilePath:Vd.relative(t.rootDir,c)}}catch(p){x.error(`Failed to read and resolve test at '${c}': ${p}`),process.exit(1)}});return Promise.all(l).then(uP)}import{randomUUID as bP}from"crypto";import{cloneDeep as Ub}from"lodash-es";async function Nb({orgId:n,codeEvalTools:e,logger:t,outputDefinitions:r,testContext:o}){let i={};for(let s of r){let{name:a,value:l}=s;i[a]=await $t({orgId:n,s:l,localTools:e,logger:t,context:o})}return i}async function Mb({baseUrl:n,envName:e,devicePixelRatio:t,apiClient:r,test:o,storageClient:i,codeEvalTools:s,generator:a,orgId:l,variables:c,logger:p,customHeaders:d,testInputs:m,analytics:u,localBrowserConfigFromEnv:h,visualDiffScreenshotStorage:f}){let g=await Hs({advanced:{...h,...o.advanced},customHeaders:d,envVariables:c,envName:e,baseUrl:n,logger:p,localTools:s,orgId:l}),y=await qr.init(l),S={baseUrl:r.baseUrl,apiKey:r.apiKey};g.browserType==="Google Chrome"&&await xr(["chrome"]);let b;try{b=await Wt.init({baseUrl:n,logger:p,userBrowserSettings:g,storage:i,featureFlagStore:y,enricher:new hr(S),timeout:g.pageLoadTimeoutMs,contextArgs:{viewport:o.advanced.viewport??zt,deviceScaleFactor:t}})}catch(C){let I=C.message;if(I.includes("Executable doesn't exist")||I.includes("install your dependencies"))x.error("The headless browser used by Momentic is not installed correctly. Re-installing the necessary dependencies before starting the test. If this issue persists, pleases contact Momentic Support."),await xr(zd,!0),b=await Wt.init({baseUrl:n,logger:p,userBrowserSettings:g,storage:i,featureFlagStore:y,enricher:new hr(S),timeout:g.pageLoadTimeoutMs,contextArgs:{viewport:o.advanced.viewport??zt,deviceScaleFactor:t}});else throw C}let v=new Zr({browser:b,generator:a,logger:p,orgId:l,scratchPadId:void 0,storage:i,flagStore:y,localCodeEvalTools:s,visualDiffScreenshotStorage:f,analytics:u}),E=new yt({baseUrl:n,currentUrl:v.browser.url(),variablesFromEnvironment:c,envName:e});return o.parameters&&await Promise.all(o.parameters.map(async C=>{let{name:I,defaultValue:O,required:L}=C,q=m?.[I];L&&q===void 0&&(x.error(`Required parameter '${I}' is required by test '${o.name}' but not provided`),process.exit(1));let ae=await $t({orgId:l,s:q??O,localTools:s,logger:p,context:yt.dummyContext(E.getEnvName())});E.setMomenticSystemVariable(I,ae)})),{controller:v,context:E,flagStore:y}}import{randomUUID as fP}from"crypto";import{diff as gP}from"deep-object-diff";import{cloneDeep as Db}from"lodash-es";import{debounce as yP}from"ts-debounce";async function _b({testId:n,apiClient:e,testAdvancedSettings:t,aiSettingsFromEnv:r,logger:o,noReport:i}){if(i)return o.debug("The CLI is not running in a CI environment or --no-report was explicitly passed"),!1;if(t.failureRecovery===!1)return o.debug("Test advanced options has failure recovery explicitly disabled"),!1;if(t.failureRecovery===void 0&&!r?.failureRecovery)return o.debug("Test advanced options is configured to inherit failure recovery settings from the environment, but the environment has disabled it"),!1;let s;try{s=await e.getPastTestResults(n,{afterTime:Date.now()-7*24*60*60*1e3})}catch(l){return o.error({err:l},"Test is not eligible for recovery since we failed to fetch the recent test results"),!1}return s.some(l=>l.status==="PASSED")?!0:(o.debug({recentRuns:s},"Test is not eligible for recovery because there are only failures in the past 7 days"),!1)}async function Fb({attemptInputs:n,attemptFixtures:e,attemptMetadata:t}){let{attemptNumber:r,orgId:o,runId:i}=t,{controller:s,context:a,flagStore:l,analytics:c,codeEvalTools:p,storageClient:d,debugDataClient:m,logger:u,apiClient:h,billingReporter:f}=e,{test:g,orgSettings:y,noReport:S}=n;u.info(`Running test '${g.name}' locally${S?"":` and reporting results to https://app.momentic.ai/runs/${i}`}`);let b=Db(g.steps),v=async P=>{if(!(S||!i))try{if(P.results){let j=Db(P.results);Ps(j,u),P.results=j}await h.updateRun(i,{...P,updatedAt:new Date})}catch(j){u.warn({err:j},"Failed to update run data. You may see stale data on the Momentic Cloud Server.")}},E=yP(async P=>{S||await v(P)},1e4,{maxWait:3e4}),C=async P=>{try{await E(P)}catch{}},I={controller:s,storage:d,debugDataStorage:m,billingReporter:f,analytics:c,context:a,logger:u,codeEvalTools:p},O={orgId:o,runId:i||fP(),testMetadata:g,steps:g.steps},L={collectDebugData:!0,reinitializeBrowser:!0,disableHealing:!await _b({noReport:S,currentAttempt:r,testId:g.id,flagStore:l,apiClient:h,testAdvancedSettings:g.advanced,aiSettingsFromEnv:y.ai,logger:u})},q={step:{},test:{onTestComplete:async()=>{await s.browser.cleanup()},onSaveScreenshot:async P=>{if(S)return"";let{key:j}=await h.uploadScreenshot({screenshot:P.toString("base64")});return j},onUpdateRun:P=>{C(P)},onSaveFinalRunResults:async P=>{E.cancel(),await v(P)},onProposedTestSteps:async P=>h.uploadProposedSteps(P,u),onTestSuccess:async P=>{let j=gP(b,P);if(Object.keys(j).length===0){u.debug("No changes to test steps after success");return}u.debug({changes:j},"Updating steps post-run success in worker");try{let{cachesToSave:_}=await Et({steps:P,cacheCreationParams:{testId:g.id,orgId:o}});await h.updateStepCaches({testId:g.id,entries:_})}catch(_){u.error({err:_},"Failed to save step caches after successful execution. This is not critical, but can impact future performance.")}}}};return await f.reportBillableEvent(u,"test-run",{eventId:i,testId:g.id}),await $s({fixtures:I,inputs:O,options:L,callbacks:q})}async function zb(n){let e=new Date,{testDefinition:t}=n;try{return await wP(n)}catch(r){let o="Fatal error running test";return x.error(`${o}: ${r.message}`),H.error({err:r},o),{parameters:n,failureReason:"UnknownError",failureDetails:{errorMessage:r.message,errorStack:r.stack},status:"FAILED",attempts:0,test:t,filePath:t.relativeFilePath,startedAt:e,finishedAt:new Date,outputs:{}}}}async function wP(n){let{testDefinition:e,project:t,apiClient:r,orgId:o,noReport:i,urlOverride:s,runGroupId:a,runSigIntHandlers:l}=n,c=new $r(r,o),p=e.steps,d=Ub(e.steps);try{await c.resolveStepCacheEntries({organizationId:o,testId:e.id,steps:d,schemaVersion:e.schemaVersion,logger:H})}catch(y){throw H.error({err:y},"Failed to resolve step cache entries"),new Error(`Failed to resolve step cache entries. Please ensure you are running using a supported version of Momentic. If you believe this is an error, please contact Momentic Support with the following error: ${y}`)}let m=n.envName??vP(e),u,h={};if(m){try{u=ui(m,t,H)}catch(y){let S=`Failed to resolve environment ${m} for test ${e.name}: ${y}`;throw new Error(S)}h=u.variables}let f=e.baseUrl;if(s)f=s;else if(!f){let y=h[be];typeof y=="string"&&(f=y)}if(!f){let y=`Cannot run test with no base URL and no ${be} variable defined in its environment`;throw new Error(y)}let g;if(!i)try{g=(await r.createRun({stepsSnapshot:p,runGroupId:a,testId:e.id,testName:e.name,trigger:"CLI",resolvedBaseUrl:f,schemaVersion:e.schemaVersion})).id}catch(y){throw H.error({err:y,testId:e.id},"Error creating run on Momentic Cloud"),new Error(`Error creating run on Momentic Cloud. Please ensure Momentic is accessible from your environment.
|
|
2943
2943
|
If you believe this is an error, please contact Momentic Support with the following error message: ${y}`)}try{return l?.push(async()=>{!g||i||await r.updateRun(g,{status:"CANCELLED",updatedAt:new Date})}),SP({...n,variables:h,envName:m,runId:g,stepsWithCaches:d,resolvedEnv:u,baseUrl:f,storageClient:c})}finally{l?.pop()}}async function SP(n){let{testDefinition:e,stepsWithCaches:t,project:r,regenerateGoldenFiles:o,apiClient:i,generator:s,baseUrl:a,storageClient:l,runId:c,orgId:p,envName:d,urlOverride:m,customHeaders:u,testInputs:h,variables:f,analytics:g,resolvedEnv:y,noReport:S,retriesOverride:b,billingReporter:v,devicePixelRatio:E,logUpdate:C}=n,I=new Date,O=new fo(r,o),L={ai:r.config.ai},q=new Fr(i,p),ae=new On({httpClient:new on({baseUrl:i.baseUrl,apiKey:i.apiKey})}),P={envName:d,urlOverride:m,customHeaders:u,testInputs:h};c&&(f[Oh]=c);let j=H.child({testId:e.id,runId:c,orgId:p}),_=g.child({test_id:e.id,test_name:e.name,label_names:e.labels,run_id:c});_.track({type:"execution:test_start"});let me=async ye=>{if(!(S||!c))try{await i.updateRun(c,ye)}catch(ne){j.warn({err:ne},`Failed to update run status for test ${c}. You may see stale data on the Momentic Cloud Server.`)}},k,ge=Math.max(b??e.retries,0),ie=[];j.info("Starting test run using CLI");for(let ye=0;ye<=ge;ye++){let ne=bP(),he={...e,steps:Ub(t)};if(!S&&c)try{ne=(await i.createRunAttempt(c)).id}catch(Ne){throw j.error({err:Ne,testId:he.id,runId:c},"Error creating run attempt on Momentic Cloud"),new Error("Error creating run attempt on Momentic Cloud. Please ensure Momentic is accessible from your environment or pass the --no-report flag.")}ye!==0&&C("RETRY",`attempt ${ye+1}/${ge+1}`);let it=async Ne=>{if(!(S||!c))try{await i.updateRunAttempt(c,ne,Ne)}catch(N){j.warn({err:N},`Failed to update run attempt for attempt ${ne}. You may see stale data on the Momentic Cloud Server.`)}};try{await me({status:"RUNNING",attempts:ye+1});let{controller:Ne,context:N,flagStore:Yt}=await Mb({baseUrl:a,envName:d,apiClient:i,devicePixelRatio:E,logger:j,storageClient:l,codeEvalTools:ae,test:he,generator:s,orgId:p,variables:f,customHeaders:u,testInputs:h,localBrowserConfigFromEnv:y?.browser,visualDiffScreenshotStorage:O,analytics:_});k=await Fb({attemptMetadata:{attemptNumber:ye+1,orgId:p,runId:c},attemptFixtures:{logger:j,storageClient:l,billingReporter:v,analytics:_,debugDataClient:q,codeEvalTools:ae,flagStore:Yt,apiClient:i,context:N,controller:Ne},attemptInputs:{test:he,orgSettings:L,noReport:S}}),await it({status:k.status,finishedAt:new Date}),ie.unshift(k.status);let de=await Nb({orgId:p,codeEvalTools:ae,logger:j,outputDefinitions:e.outputs??[],testContext:N}),Fe=new Date,vt=su(ie),We=Fe.getTime()-I.getTime(),Xt=ye+1;if(k.status!=="FAILED")return k.status==="PASSED"&&_.track({type:"execution:test_success",attempt_count:Xt,duration_ms:We,is_flake:vt}),await me({status:k.status,finishedAt:Fe,flake:vt}),{...k,parameters:P,runId:c,test:he,filePath:he.relativeFilePath,startedAt:I,finishedAt:Fe,attempts:Xt,baseUrl:a,outputs:de};let Gn=k.failedStepResult,un=Gn?.message||"Unknown failure";if(ye<ge){j.warn({errResult:Gn},`Test '${he.name}' failed attempt ${ye+1} of ${ge+1}, retrying...`);continue}j.error({errResult:Gn,testName:he.name,numAttempts:ge+1,errorMessage:un},"Test failed after all exhausting attempts using CLI");let To={errorMessage:un},Vn=Gn?.failureReason??Du(un)??"UnknownError";return await me({status:"FAILED",finishedAt:Fe,failureDetails:To,failureReason:Vn}),_.track({type:"execution:test_fail",attempt_count:ye+1,duration_ms:Fe.getTime()-I.getTime(),fail_reason:To.errorMessage}),{...k,parameters:P,runId:c,failureDetails:To,failureReason:Vn,test:he,filePath:he.relativeFilePath,startedAt:I,finishedAt:new Date,attempts:ye+1,baseUrl:a,outputs:de}}catch(Ne){let N=`Encountered fatal platform error while running test '${he.name}': ${Ne}`,Yt=new Date,de=ye+1;j.error({err:Ne},N),x.error(N);let Fe={errorMessage:Ne.message,errStack:Ne.stack},vt={status:"FAILED",failureDetails:Fe,failureReason:"InternalPlatformError",finishedAt:Yt};return await Promise.all([it(vt),me(vt)]),_.track({type:"execution:test_fail",attempt_count:de,duration_ms:Yt.getTime()-I.getTime(),fail_reason:Fe.errorMessage}),{...vt,parameters:P,runId:c,test:he,filePath:he.relativeFilePath,startedAt:I,finishedAt:new Date,attempts:de,baseUrl:a,outputs:{}}}}throw new Error("This code should not be reachable")}function vP(n){for(let e of n.envs??[])if(e.default)return e.name}async function Bb(n){let{tests:e,yes:t,start:r,waitOn:o,client:i,billingReporter:s,analytics:a,project:l,report:c,retriesOverride:p,urlOverride:d,envName:m,orgId:u,devicePixelRatio:h,customHeaders:f,testInputMatrix:g,reporter:y,include:S,exclude:b,labels:v,reporterDir:E="reports",waitOnTimeout:C=60,parallel:I=1,shardIndex:O=1,shardCount:L=1,regenerateGoldenFiles:q}=n;r&&(H.info({orgId:u},`Executing start command: ${r}`),await Gy(r,!1)),o&&(H.info({orgId:u},`Waiting for url: ${o} with timeout: ${C} seconds.`),await TP({resources:[o],interval:2500,timeout:C*1e3,headers:{Accept:"*/*"},followRedirect:!0,verbose:!1,log:!0,strictSSL:!1}));let ae=new Qr({baseUrl:i.baseUrl,apiKey:i.apiKey}),P=pt(l,x),j=await kb({tests:e,momenticFiles:P,yes:t,project:l,include:S,exclude:b,labels:v}),_=[];j.forEach((de,Fe)=>{g?g.forEach((vt,We)=>{_.push({testIndex:Fe,inputs:vt,inputIndex:We})}):_.push({testIndex:Fe,inputs:void 0,inputIndex:void 0})}),L&&L>1&&(_=EP(_,O,L));let me=`Running ${_.length} tests with ${I} workers`;H.info({allTestsToRunWithInputs:_,shardCount:L,shardIndex:O,orgId:u},me),x.dimmed(me),_.forEach(de=>{x.dimmed(` - ${[j[de.testIndex].relativeFilePath]}${typeof de.inputIndex=="number"?` with input set ${de.inputIndex}`:""}`)}),x.log("");let k=[],ge=Date.now(),ie=new Set,ye=()=>{let de=i.getAppUrl(),Fe=So({results:k,startTime:ge,onFailed:We=>{$a(We,We.filePath)},getDisplayLine:We=>{let Xt=` - ${We.proposedTest?"[AUTO-HEALED] ":""}${We.filePath}`;return c&&We.runId&&(Xt+=` (${de}/runs/${We.runId})`),Xt},entity:"test"}),vt=k.filter(We=>We.proposedTest);return vt.length>0&&x.warn(`${vt.length} tests passed with auto-healing. Please use the run links printed above to review proposed changes and apply them locally.`),Fe},ne=await Lb(),he=a.child({commit_sha:ne?.gitCommitSha,commit_sha_short:ne?.gitCommitShaShort,branch_name:ne?.gitBranchName,origin_url:ne?.gitOriginUrl,commit_timestamp:ne?.gitCommitTimestamp,github_repository:ne?.githubRepository,gitlab_project_path:ne?.gitlabProjectPath}),it;c&&(it=(await i.createRunGroup({...ne,trigger:en.CLI,startedAt:new Date,status:"RUNNING"})).id);let Ne=[],N=async()=>{x.warn("SIGINT received. Stopping tests and printing latest results..."),c&&it&&await i.updateRunGroup(it,{status:"CANCELLED",finishedAt:new Date}),ye(),await Promise.allSettled(Ne.map(de=>de())),process.exit()};process.on("SIGINT",N);let Yt={};for(let de=0;de<_.length;de++){let Fe=Object.values(Yt);Fe.length===I&&await Promise.race(Fe.map(Xt=>Xt.promise));let vt=_[de],We=`test-${de}`;Yt[We]={done:!1,promise:(async({testIndex:Xt,inputs:Gn})=>{let un=j[Xt];ie.add({testIndex:Xt,inputs:Gn});let To=un.relativeFilePath.includes("..")?un.fullFilePath:un.relativeFilePath,Vn=(Je,qd)=>{let Tr=(Ga,Kd)=>{let Gb=Math.floor((Kd-Ga.length)/2);return Ga.padStart(Gb+Ga.length).padEnd(Kd)};Je=Je.toUpperCase();let mn=Je;Je.includes("FAIL")?mn=xo.bgRed.white(Tr("FAIL",8)):Je.includes("PASS")?mn=xo.bgGreen.white(Tr("PASS",8)):Je.includes("START")?mn=xo.bgBlue.white(Tr("START",8)):Je.includes("CANCEL")?mn=xo.bgYellow.white(Tr("CANCEL",8)):Je.includes("RETRY")?mn=xo.bgYellow.white(Tr("RETRY",8)):Je.includes("RUN")||Je.includes("PROG")?mn=xo.bgMagenta.white(Tr("RUNNING",8)):H.warn(`Unknown status tried to be logged in run test locally: ${Je}`),xP||(mn=`[${mn}]`),x.log(`${mn} ${To} ${qd?`${qd} `:""}(${ie.size}/${_.length})`)};Vn("START");let Wb=setInterval(()=>Vn("RUN"),5*60*1e3);try{let Je=await zb({testDefinition:un,project:l,testInputs:Gn,orgId:u,devicePixelRatio:h,apiClient:i,billingReporter:s,analytics:he,generator:ae,retriesOverride:p,urlOverride:d,envName:m,noReport:!c,customHeaders:f,regenerateGoldenFiles:q,logUpdate:Vn,runGroupId:it,runSigIntHandlers:c?Ne:void 0});Vn(Je.status),k.push(Je)}catch(Je){x.error(`Encountered unexpected fatal error when running test '${un.name}': ${Je.message}`)}finally{clearInterval(Wb),Yt[We].done=!0,delete Yt[We]}})(vt)}}if(await Promise.allSettled(Object.values(Yt).map(de=>de.promise)),c&&it){let de="PASSED";k.some(Fe=>Fe.status==="FAILED")&&(de="FAILED"),await i.updateRunGroup(it,{status:de,finishedAt:new Date})}return process.off("SIGINT",N),y&&await Ob(y,{suiteName:`on-demand-suite-${ge}`,startedAt:new Date(ge),finishedAt:new Date,runs:k},E??"reports"),ye()}function EP(n,e,t){if(t>n.length&&(x.warn(`Shard count ${t} is greater than the number of tests ${n.length}! Some workers won't have any tests to run.`),t=Math.max(t,n.length),e>t))return[];let r=Math.floor((e-1)*(n.length/t)),o=Math.floor(e*(n.length/t));return n.sort().filter((s,a)=>a>=r&&a<o)}var jb=new bo({platform:"cli"},{flushAt:1,flushInterval:0});qa||H.warn("Sentry is not enabled in this environment due to unsupported node version");await Vy(H);var It=new CP;It.name("momentic").description("CLI").version(Ni||"unknown");It.command("install-browsers").option("-f, --force","Force reinstallation even if the browser executables already exist on disk.").argument("[browsers...]","Browsers to install",["chromium"]).action(async(n,e)=>{await xr(wb(n),e.force)});It.addOption(new Ut("--log-level <level>").choices(["debug","info","warn","error"]).default("info")).on("option:log-level",n=>{H.setMinLevel(n.toLowerCase()),x.setMinLevel(n.toLowerCase())});It.addOption(new Ut("--verbose","enable verbose logging")).on("option:verbose",()=>{H.enableConsoleLogs(),x.setMinLevel(0)});It.command("check-config").addOption(wo).action(async n=>{Tn({configFilePath:n.config})});var OP=It.command("migrate").description("Migrate and upgrade tooling");OP.command("v0-v1").addOption(Hn).addOption($n).action(async n=>{let e=await Wn(n);if(!e.yes&&!await Xe("This command will migrate and then delete your previous Momentic files. All members of your team should transition to the V1 CLI at the same time. Please backup your local directory for safety before proceeding. Continue?",!0)&&process.exit(1),!rb().environments?.length&&await Xe("In the V1 CLI, all environment configuration should be committed to a central `momentic.config.yaml` file, which you should commit to your source control repository. Sensitive data can be managed through `.env` files or by injecting the secret into the environment and then referencing them in `envVariables` with ${VAR} syntax. Would you like to pull the latest environments from Momentic Cloud into your `momentic.config.yaml` file?",!0)){let{apiKey:r,server:o}=e,i=Tn({}),s=new tt({baseUrl:o,apiKey:r,logger:H});await ka({client:s,project:i,skipPrompts:e.yes}),x.success("Successfully imported environments from Momentic Cloud.")}});It.command("import-from-cloud").addOption($n).addOption(vr).addOption(wo).addOption(Hn).action(async n=>{let e=await Wn(n),{apiKey:t,server:r,config:o,yes:i}=e,s=Tn({configFilePath:o}),a=new tt({baseUrl:r,apiKey:t,logger:H});await nb({client:a,project:s,skipPrompts:i}),process.exit(0)});It.command("init").description("Initialize an empty Momentic project in the current working directory").addOption(new Ut("--name <name>","Name of the project")).action(async n=>{x.info(`Welcome to the Momentic project setup wizard! \u{1F680}
|
|
2944
|
-
`),x.info("This wizard will help you bootstrap a new Momentic project. If need to import existing assets from Momentic Cloud, you can call the 'import-from-cloud' command after initialization."),$b.existsSync(uo)&&(x.error("A momentic.config.yaml file already exists in this directory. Please rename or remove it to initialize a new project."),process.exit(1));let t={name:n.name??await Xy("Choose an identifier for your project, such as a service, product, or team name (default: 'app'):","app"),include:Pi};mo(t,uo),x.success(`Initialized Momentic project file at ${Wa.resolve(uo)}`)});It.command("app").addOption($n).addOption(vr).addOption(Hn).addOption(Dd).addOption(db).addOption(wo).action(async n=>{let e=await Wn(n),{apiKey:t,port:r=Ma,yes:o,server:i,pixelRatio:s}=e,a=new tt({baseUrl:i,apiKey:t,logger:H});await Bd({client:a,skipPrompts:o,installBrowsers:!0});let l=PP(import.meta.url),c=Wa.dirname(l),p=Wa.resolve(c,"..","static"),d=Wa.resolve(c,"..","assets"),m=s??Cd();Ad(m),Eo(),await $y({momenticServerUrl:i,apiKey:t,serverPort:r,appPort:r,staticDir:p,assetsDir:d,devicePixelRatio:m,version:"1.0.
|
|
2944
|
+
`),x.info("This wizard will help you bootstrap a new Momentic project. If need to import existing assets from Momentic Cloud, you can call the 'import-from-cloud' command after initialization."),$b.existsSync(uo)&&(x.error("A momentic.config.yaml file already exists in this directory. Please rename or remove it to initialize a new project."),process.exit(1));let t={name:n.name??await Xy("Choose an identifier for your project, such as a service, product, or team name (default: 'app'):","app"),include:Pi};mo(t,uo),x.success(`Initialized Momentic project file at ${Wa.resolve(uo)}`)});It.command("app").addOption($n).addOption(vr).addOption(Hn).addOption(Dd).addOption(db).addOption(wo).action(async n=>{let e=await Wn(n),{apiKey:t,port:r=Ma,yes:o,server:i,pixelRatio:s}=e,a=new tt({baseUrl:i,apiKey:t,logger:H});await Bd({client:a,skipPrompts:o,installBrowsers:!0});let l=PP(import.meta.url),c=Wa.dirname(l),p=Wa.resolve(c,"..","static"),d=Wa.resolve(c,"..","assets"),m=s??Cd();Ad(m),Eo(),await $y({momenticServerUrl:i,apiKey:t,serverPort:r,appPort:r,staticDir:p,assetsDir:d,devicePixelRatio:m,version:"1.0.67"});let u=`http://localhost:${r}`;await IP(u)});var Hb=It.command("queue").description("Queue tests or suites to run on Momentic Cloud");Hb.command("suites").description("Run one or more suites on Momentic Cloud").addOption($n).addOption(vr).addOption(Nd).addOption(Md).addOption(Hn).addArgument(fb).addOption(Fa).addOption(Da).addOption(_a).action(async(n,e)=>{let{apiKey:t,server:r,wait:o,waitTimeout:i,env:s,urlOverride:a,customHeaders:l}=await Wn(e),c=new tt({baseUrl:r,apiKey:t,logger:H});(!n||!Array.isArray(n)||!n.length)&&(x.error("Must pass at least one suite to run."),process.exit(1));let{orgId:p}=await c.getAuthInfo();await Tb({client:c,orgId:p,wait:o,suitePaths:n,waitTimeout:i,env:s,urlOverride:a,customHeaders:Ua(l)})});Hb.command("tests").description("Run one or more tests on Momentic Cloud").addOption($n).addOption(vr).addOption(Hn).addOption(_a).addOption(Fd).addOption(Fa).addOption(Da).addOption(Nd).addOption(Md).addArgument(mb).action(async(n,e)=>{let t=await Wn(e),{all:r,apiKey:o,customHeaders:i,env:s,server:a,inputCsv:l,urlOverride:c,wait:p,waitTimeout:d,yes:m}=t,u=Ua(i);for(let y of n)(y.endsWith(".yaml")||$b.existsSync(y))&&x.warn("Are you trying to run a test on your local machine? If so, please use the 'run' command instead of the 'queue' command");let h=new tt({baseUrl:a,apiKey:o,logger:H}),{orgId:f}=await h.getAuthInfo(),g;l&&(g=await Id(l)),await Eb({client:h,orgId:f,tests:n,all:r,customHeaders:u,env:s,urlOverride:c,wait:p,waitTimeout:d,testInputMatrix:g,yes:m}),process.exit(0)});var LP=It.command("run").alias("test").description("Run tests on the local machine");LP.addOption($n).addOption(vr).addOption(wo).addOption(Ud).addOption(Hn).addOption(_a).addOption(Fd).addOption(Da).addOption(Fa).addOption(_d).addOption(Dd).addOption(new Ut("--start <start>","Arbitrary setup command that will run before Momentic steps begin.")).addOption(new Ut("--wait-on <waitOn>","URL to wait to become accessible before Momentic tests begin.")).addOption(new Ut("--wait-on-timeout <waitOnTimeout>","Max time in seconds to wait for the --wait-on URL to become accessible.")).addOption(new Ut("--retries <retries>","Number of retries to attempt when running tests locally. Defaults to each test's own retry configuration.")).addOption(new Ut("-p, --parallel <parallel>","When running with the --local flag, the number of tests to run in parallel. Defaults to 1.")).addOption(new Ut("--labels <labels...>","Only run tests with the specified label(s).")).addOption(new Ut("--update-golden-files","Update locally stored golden files for steps that this is enabled for.")).addOption(_d).addOption(sb).addOption(ab).addOption(pb).addOption(ub).addOption(lb).addOption(cb).addArgument(hb).action(async(n,e)=>{let t=await Wn(e),r=Ua(t.customHeaders),o=Tn({configFilePath:t.config,nameFilter:t.filter}),i=new tt({baseUrl:t.server,apiKey:t.apiKey,logger:H}),s=new Cs(i),a;t.inputCsv&&(a=await Id(t.inputCsv));let{orgId:l,userId:c}=await Bd({client:i,skipPrompts:t.yes,installBrowsers:!0}),p=t.pixelRatio??Cd();Ad(p);let d=jb.child({org_id:l,user_id:c});t.report||(d=new Pa);try{(await Bb({...t,retriesOverride:t.retries,devicePixelRatio:p,tests:n,project:o,client:i,billingReporter:s,analytics:d,customHeaders:r,envName:t.env,orgId:l,testInputMatrix:a,regenerateGoldenFiles:t.updateGoldenFiles})).failed>0?process.exit(1):process.exit(0)}catch(m){x.error("Failed to run tests locally. Please check the error message below or run with the --verbose flag."),x.error(m),process.exit(1)}});var kP=It.command("apply").description("Apply an operation to local resources");kP.command("patch").addOption($n).addOption(vr).addOption(wo).addOption(Ud).addOption(Hn).addOption(new Ut("--from <from>","Name or ID of the patch to apply").makeOptionMandatory()).addOption(new Ut("--to <to>","Name or ID of the test to apply the patch to").makeOptionMandatory()).action(async n=>{let e=await Wn(n),{apiKey:t,server:r,config:o,yes:i}=e,s=Tn({configFilePath:o}),a=new tt({baseUrl:r,apiKey:t,logger:H}),l=pt(s,x),c=l.tests[n.to]??Object.values(l.tests).find(d=>Pe(d.name)===n.to.trim());c||(x.error(`No test matching '${n.to}' could be found in the current project.`),process.exit(1));let p=await a.fetchTestFragment(n.from);await Jy({client:a,test:c,fragment:p,yes:i,entities:l,logger:H}),process.exit(0)});async function NP(){try{await It.parseAsync(process.argv),Eo()}catch(n){let e={};try{e.playwrightVersion=AP("npx playwright --version").toString()}catch(t){H.error({err:t},"Error fetching debug information")}H.error({err:n,debugInfo:e},"Uncaught error in CLI"),H.flush(),Va(n,e),x.error(n),Eo(),process.exit(1)}}RP.setMaxListeners(50);process.on("warning",n=>{H.warn({err:n},`Node warning received on CLI: ${n.message}`)});NP();
|
|
2945
2945
|
//# sourceMappingURL=cli.js.map
|
|
2946
2946
|
//# debugId=91375334-fe69-58c5-b5fb-6a850da4c13c
|