@checksum-ai/runtime 1.0.8 → 1.0.10
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/cli.js +19 -0
- package/package.json +3 -2
- package/postinstall.js +1 -1
- package/cli.ts +0 -599
- package/types.ts +0 -58
package/cli.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
var m=Object.create;var p=Object.defineProperty;var f=Object.getOwnPropertyDescriptor;var C=Object.getOwnPropertyNames;var w=Object.getPrototypeOf,y=Object.prototype.hasOwnProperty;var k=(c,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of C(t))!y.call(c,o)&&o!==e&&p(c,o,{get:()=>t[o],enumerable:!(s=f(t,o))||s.enumerable});return c};var P=(c,t,e)=>(e=c!=null?m(w(c)):{},k(t||!c||!c.__esModule?p(e,"default",{value:c,enumerable:!0}):e,c));var n=require("fs"),l=P(require("child_process")),r=require("path");var d="checksum";var g=class{constructor(){this.UPLOAD_AGENT_PATH=(0,r.join)(__dirname,"upload-agent.js");this.CHECKSUM_API_URL="http://localhost:3000";this.didFail=!1;this.mock=!0;this.completeIndicators={upload:!1,tests:!1,report:!1};this.guardReturn=async(t,e=1e3,s="action hang guard timed out")=>{let o="guard-timed-out",i=async()=>(await this.awaitSleep(e+1e3),o),a=await Promise.race([t,i()]);if(typeof a=="string"&&a===o)throw new Error(s);return a};this.awaitSleep=t=>new Promise(e=>setTimeout(e,t))}async execute(){var t;switch(process.argv[2]){case"init":this.install();break;case"test":if(((t=process.argv)==null?void 0:t[3])==="--help"){await this.printHelp("test");break}await this.test(process.argv.slice(3));break;case"show-report":this.showReport(process.argv.slice(3));break;default:await this.printHelp()}process.exit(0)}async execCmd(t){let e=await l.spawn(t,{shell:!0,stdio:"inherit"});return new Promise((o,i)=>{e.on("exit",a=>{a===0?o(!0):i(new Error(`Checsum failed execution with code: ${a} `))})})}async getCmdOutput(t){return new Promise(function(e,s){l.exec(t,(o,i,a)=>{if(o){s(`Error executing command: ${o.message}`);return}e(i)})})}async printHelp(t){switch(t){default:console.log(`
|
|
2
|
+
Checksum CLI
|
|
3
|
+
Usage: checksum [command] [options]
|
|
4
|
+
|
|
5
|
+
Commands:
|
|
6
|
+
init installs checksum files and folders
|
|
7
|
+
test [options] [test-filter...] runs checksum tests
|
|
8
|
+
help prints this help message
|
|
9
|
+
show-report [options] [report] show HTML report
|
|
10
|
+
`);break;case"test":try{let e="npx playwright test --help",s=(await this.getCmdOutput(e)).replace(/npx playwright/g,"yarn checksum").split(`
|
|
11
|
+
`);s.splice(5,0," --checksum-config=<config> Checksum configuration in JSON format").join(`
|
|
12
|
+
`),console.log(s.join(`
|
|
13
|
+
`))}catch(e){console.log("Error",e.message)}break}}async showReport(t){let e=`npx playwright show-report ${t.join(" ")}`;try{await this.execCmd(e)}catch(s){console.log("Error showing report",s.message)}}async test(t){t=this.getChecksumConfigFromCommand(t),this.setChecksumConfig(),await this.getSession();let e;try{let{uuid:o,uploadURL:i}=this.testSession;e=await this.guardReturn(this.startUploadAgent(o,i),1e4,"Upload agent timeout")}catch{console.log("Error starting upload agent. Test results will not be available on checksum.")}this.buildVolatileConfig();let s=`${e?`CHECKSUM_UPLOAD_AGENT_PORT=${e} `:""} PWDEBUG=console npx playwright test --config ${this.getPlaywrightConfigFile()} ${t.join(" ")}`;try{await this.execCmd(s)}catch(o){this.didFail=!0,console.log("Error during test",o.message)}finally{let o=this.getPlaywrightReportPath();(0,n.existsSync)(o)?this.uploadAgent.stdin.write(`cli:report=${o}`):console.log(`Could not find report file at ${o}`),this.completeIndicators.tests=!0,await this.handleCompleteMessage()}}getPlaywrightReportPath(){var o,i;let t=(0,r.join)(process.cwd(),"playwright-report"),e=require(this.getPlaywrightConfigFile()),{reporter:s}=e;return s instanceof Array&&s.length>1&&((o=s[1])!=null&&o.outputFolder)&&(t=(i=s[1])==null?void 0:i.outputFolder),process.env.PLAYWRIGHT_HTML_REPORT&&(t=process.env.PLAYWRIGHT_HTML_REPORT),(0,r.join)(t,"index.html")}getPlaywrightConfigFile(){return(0,r.join)(this.getRootDirPath(),"playwright.config.ts")}startUploadAgent(t,e){return new Promise((s,o)=>{console.log("Starting upload agent"),this.uploadAgent=l.spawn("node",[this.UPLOAD_AGENT_PATH,JSON.stringify({sessionId:t,checksumApiURL:this.CHECKSUM_API_URL,apiKey:this.config.apiKey}),...this.mock?["mock"]:[]]),this.uploadAgent.stdout.on("data",i=>{let a=i.toString().trim();if(!a.startsWith("upag:"))return;let[u,h]=a.substring(5).split("=");u==="port"?(console.log("Received port from upload agent",h),s(h)):this.handleUploadAgentMessage(u,h)}),this.uploadAgent.on("exit",(i,a)=>{console.log(`upload agent process exited with code ${i} and signal ${a}`)}),this.uploadAgent.on("error",i=>{console.error(`Error starting upload agent: ${i.message}`)})})}async handleUploadAgentMessage(t,e){switch(t){case"complete":this.sendUploadsComplete().then(()=>{this.completeIndicators.upload=!0});break;case"report-uploaded":let s=await this.sendTestrunEnd();this.completeIndicators.report=!0,s&&console.log(`*******************
|
|
14
|
+
* Checksum report URL: ${s}
|
|
15
|
+
*******************`);break;default:console.warn(`Unhandled upload agent message: ${t}=${e}`)}}async handleCompleteMessage(){for(;;)Object.keys(this.completeIndicators).find(t=>!this.completeIndicators[t])?await this.awaitSleep(1e3):(console.log("Tests complete"),this.shutdown(this.didFail?1:0))}shutdown(t=0){this.cleanup(),process.exit(t)}buildVolatileConfig(){if(!this.volatileChecksumConfig)return;let t=this.getVolatileConfigPath(),e=`
|
|
16
|
+
import { RunMode, getChecksumConfig } from "@checksum-ai/runtime";
|
|
17
|
+
|
|
18
|
+
export default getChecksumConfig(${JSON.stringify(this.config,null,2)});
|
|
19
|
+
`;(0,n.writeFileSync)(t,e)}cleanup(){this.deleteVolatileConfig(),this.uploadAgent.stdin.write("cli:shutdown"),this.uploadAgent.kill()}async getSession(){try{if(this.mock){this.testSession={uuid:"session-id-1234",uploadURL:"http://localhost:3000/upload"};return}let t=this.config.apiKey;(!t||t==="<API key>")&&(console.error("No API key found in checksum config"),this.shutdown(1));let e=JSON.stringify(await this.getEnvInfo()),s=await fetch(`${this.CHECKSUM_API_URL}/client-api/test-runs`,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json",ChecksumAppCode:t},body:e});this.testSession=await s.json()}catch(t){console.error("Error getting checksum test session",t),this.shutdown(1)}}async sendTestrunEnd(){try{if(this.mock)return"https://mock.report.url";let{uuid:t}=this.testSession,e=JSON.stringify({failed:this.didFail?1:0,passed:0,healed:0,endedAt:Date.now()}),{reportURL:s}=await this.updateTestRun(`${this.CHECKSUM_API_URL}/client-api/test-runs/${t}`,"PATCH",e);return s}catch(t){return console.log("Error sending test run end",t.message),null}}async sendUploadsComplete(){if(!this.mock)try{let{uuid:t}=this.testSession;await this.updateTestRun(`${this.CHECKSUM_API_URL}/client-api/test-runs/${t}/uploads-completed`,"PATCH")}catch(t){console.log("Error sending test run uploads complete",t.message)}}async updateTestRun(t,e,s=void 0){return(await fetch(t,{method:e,headers:{Accept:"application/json","Content-Type":"application/json",ChecksumAppCode:this.config.apiKey},body:s})).json()}async getEnvInfo(){let t={commitHash:"",branch:"branch",environment:process.env.CI?"CI":"local",name:"name",startedAt:Date.now()};try{t.commitHash=(await this.getCmdOutput("git rev-parse HEAD")).toString().trim()}catch(e){console.log("Error getting git hash",e.message)}try{t.branch=(await this.getCmdOutput("git rev-parse --abbrev-ref HEAD")).toString().trim()}catch(e){console.log("Error getting branch name",e.message)}return t}getVolatileConfigPath(){return(0,r.join)(this.getRootDirPath(),"checksum.config.tmp.ts")}deleteVolatileConfig(){let t=this.getVolatileConfigPath();(0,n.existsSync)(t)&&(0,n.rmSync)(t)}setChecksumConfig(){this.config={...require((0,r.join)(this.getRootDirPath(),"checksum.config.ts")).default||{},...this.volatileChecksumConfig||{}}}getChecksumConfigFromCommand(t){this.deleteVolatileConfig();for(let e of t)if(e.startsWith("--checksum-config"))try{return this.volatileChecksumConfig=JSON.parse(e.split("=")[1]),t.filter(s=>s!==e)}catch(s){console.log("Error parsing checksum config",s.message),this.volatileChecksumConfig=void 0}return t}install(){console.log("Creating Checksum directory and necessary files to run your tests");let t=this.getRootDirPath();if((0,n.existsSync)(this.getRootDirPath())||(0,n.mkdirSync)(t),!(0,n.existsSync)(this.getChecksumRootOrigin()))throw new Error("Could not find checksum root directory, please install @checksum-ai/runtime package");["checksum.config.ts","playwright.config.ts","login.ts","README.md"].forEach(e=>{(0,n.copyFileSync)((0,r.join)(this.getChecksumRootOrigin(),e),(0,r.join)(t,e))}),(0,n.mkdirSync)((0,r.join)(t,"tests"),{recursive:!0}),["esra","har","trace","log"].forEach(e=>{(0,n.mkdirSync)((0,r.join)(t,"test-data",e),{recursive:!0})})}getRootDirPath(){return(0,r.join)(process.cwd(),d)}getChecksumRootOrigin(){return(0,r.join)(process.cwd(),"node_modules","@checksum-ai","runtime","checksum-root")}};(async()=>await new g().execute())();
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checksum-ai/runtime",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "Checksum.ai test runtime",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"playwright
|
|
10
|
+
"@playwright/test": "1.37.0",
|
|
11
|
+
"ts-node": "^10.9.1"
|
|
11
12
|
},
|
|
12
13
|
"repository": {
|
|
13
14
|
"type": "git",
|
package/postinstall.js
CHANGED
|
@@ -13,7 +13,7 @@ try {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
packageJson.scripts["checksum"] =
|
|
16
|
-
"ts-node --esm --skipIgnore ./node_modules/@checksum-ai/runtime/cli.
|
|
16
|
+
"ts-node --esm --skipIgnore ./node_modules/@checksum-ai/runtime/cli.js";
|
|
17
17
|
|
|
18
18
|
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
19
19
|
} catch (e) {
|
package/cli.ts
DELETED
|
@@ -1,599 +0,0 @@
|
|
|
1
|
-
import { copyFileSync, existsSync, mkdirSync, rmSync, writeFileSync } from "fs";
|
|
2
|
-
import * as childProcess from "child_process";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
import {
|
|
5
|
-
CHECKSUM_ROOT_FOLDER,
|
|
6
|
-
ChecksumConfig,
|
|
7
|
-
TestSuiteSession,
|
|
8
|
-
} from "./types";
|
|
9
|
-
|
|
10
|
-
class ChecksumCLI {
|
|
11
|
-
private readonly UPLOAD_AGENT_PATH = join(__dirname, "upload-agent.js");
|
|
12
|
-
private readonly CHECKSUM_API_URL = "http://localhost:3000";
|
|
13
|
-
|
|
14
|
-
private testSession: TestSuiteSession;
|
|
15
|
-
private volatileChecksumConfig;
|
|
16
|
-
private uploadAgent;
|
|
17
|
-
private config: ChecksumConfig;
|
|
18
|
-
|
|
19
|
-
private didFail = false;
|
|
20
|
-
private mock = true;
|
|
21
|
-
|
|
22
|
-
private completeIndicators = {
|
|
23
|
-
upload: false,
|
|
24
|
-
tests: false,
|
|
25
|
-
report: false,
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
constructor() {}
|
|
29
|
-
|
|
30
|
-
// rename install to init
|
|
31
|
-
// rename run to test
|
|
32
|
-
// rename import of playwright to our alias
|
|
33
|
-
async execute() {
|
|
34
|
-
switch (process.argv[2]) {
|
|
35
|
-
case "init":
|
|
36
|
-
this.install();
|
|
37
|
-
break;
|
|
38
|
-
case "test":
|
|
39
|
-
if (process.argv?.[3] === "--help") {
|
|
40
|
-
await this.printHelp("test");
|
|
41
|
-
break;
|
|
42
|
-
}
|
|
43
|
-
await this.test(process.argv.slice(3));
|
|
44
|
-
break;
|
|
45
|
-
case "show-report":
|
|
46
|
-
this.showReport(process.argv.slice(3));
|
|
47
|
-
break;
|
|
48
|
-
default:
|
|
49
|
-
// should we simply forward to playwright in default case?
|
|
50
|
-
await this.printHelp();
|
|
51
|
-
}
|
|
52
|
-
process.exit(0);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async execCmd(cmdString) {
|
|
56
|
-
const child = await childProcess.spawn(cmdString, {
|
|
57
|
-
shell: true,
|
|
58
|
-
stdio: "inherit",
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
const exitPromise = new Promise((resolve, reject) => {
|
|
62
|
-
child.on("exit", (code) => {
|
|
63
|
-
if (code === 0) {
|
|
64
|
-
resolve(true);
|
|
65
|
-
} else {
|
|
66
|
-
reject(new Error(`Checsum failed execution with code: ${code} `));
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
return exitPromise;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async getCmdOutput(cmdString): Promise<string> {
|
|
75
|
-
return new Promise<string>(function (resolve, reject) {
|
|
76
|
-
childProcess.exec(cmdString, (error, stdout, stderr) => {
|
|
77
|
-
if (error) {
|
|
78
|
-
reject(`Error executing command: ${error.message}`);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
resolve(stdout);
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
// return promise;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async printHelp(command?: string) {
|
|
90
|
-
switch (command) {
|
|
91
|
-
default:
|
|
92
|
-
console.log(`
|
|
93
|
-
Checksum CLI
|
|
94
|
-
Usage: checksum [command] [options]
|
|
95
|
-
|
|
96
|
-
Commands:
|
|
97
|
-
init installs checksum files and folders
|
|
98
|
-
test [options] [test-filter...] runs checksum tests
|
|
99
|
-
help prints this help message
|
|
100
|
-
show-report [options] [report] show HTML report
|
|
101
|
-
`);
|
|
102
|
-
break;
|
|
103
|
-
case "test":
|
|
104
|
-
try {
|
|
105
|
-
const cmd = `npx playwright test --help`;
|
|
106
|
-
const testHelp = (await this.getCmdOutput(cmd))
|
|
107
|
-
.replace(/npx playwright/g, "yarn checksum")
|
|
108
|
-
.split("\n");
|
|
109
|
-
testHelp
|
|
110
|
-
.splice(
|
|
111
|
-
5,
|
|
112
|
-
0,
|
|
113
|
-
" --checksum-config=<config> Checksum configuration in JSON format"
|
|
114
|
-
)
|
|
115
|
-
.join("\n");
|
|
116
|
-
console.log(testHelp.join("\n"));
|
|
117
|
-
} catch (e) {
|
|
118
|
-
console.log("Error", e.message);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
break;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async showReport(args: string[]) {
|
|
126
|
-
const cmd = `npx playwright show-report ${args.join(" ")}`;
|
|
127
|
-
|
|
128
|
-
try {
|
|
129
|
-
await this.execCmd(cmd);
|
|
130
|
-
} catch (e) {
|
|
131
|
-
console.log("Error showing report", e.message);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async test(args: string[]) {
|
|
136
|
-
// check for checksum config in command
|
|
137
|
-
args = this.getChecksumConfigFromCommand(args);
|
|
138
|
-
|
|
139
|
-
// load checksum config
|
|
140
|
-
this.setChecksumConfig();
|
|
141
|
-
|
|
142
|
-
// init new test session
|
|
143
|
-
await this.getSession();
|
|
144
|
-
|
|
145
|
-
// start upload agent
|
|
146
|
-
let uploadAgentListeningPort;
|
|
147
|
-
try {
|
|
148
|
-
const { uuid, uploadURL } = this.testSession;
|
|
149
|
-
// load upload agent, timout after 20 seconds
|
|
150
|
-
uploadAgentListeningPort = await this.guardReturn(
|
|
151
|
-
this.startUploadAgent(uuid, uploadURL),
|
|
152
|
-
10_000,
|
|
153
|
-
"Upload agent timeout"
|
|
154
|
-
);
|
|
155
|
-
} catch (e) {
|
|
156
|
-
console.log(
|
|
157
|
-
"Error starting upload agent. Test results will not be available on checksum."
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// write volatile config if set
|
|
162
|
-
this.buildVolatileConfig();
|
|
163
|
-
|
|
164
|
-
// build shell command
|
|
165
|
-
const cmd = `${
|
|
166
|
-
uploadAgentListeningPort
|
|
167
|
-
? `CHECKSUM_UPLOAD_AGENT_PORT=${uploadAgentListeningPort} `
|
|
168
|
-
: ""
|
|
169
|
-
} PWDEBUG=console npx playwright test --config ${this.getPlaywrightConfigFile()} ${args.join(
|
|
170
|
-
" "
|
|
171
|
-
)}`;
|
|
172
|
-
|
|
173
|
-
try {
|
|
174
|
-
// run tests
|
|
175
|
-
await this.execCmd(cmd);
|
|
176
|
-
} catch (e) {
|
|
177
|
-
this.didFail = true;
|
|
178
|
-
console.log("Error during test", e.message);
|
|
179
|
-
} finally {
|
|
180
|
-
const reportFile = this.getPlaywrightReportPath();
|
|
181
|
-
if (!existsSync(reportFile)) {
|
|
182
|
-
console.log(`Could not find report file at ${reportFile}`);
|
|
183
|
-
} else {
|
|
184
|
-
this.uploadAgent.stdin.write(`cli:report=${reportFile}`);
|
|
185
|
-
}
|
|
186
|
-
// upload report
|
|
187
|
-
this.completeIndicators.tests = true;
|
|
188
|
-
await this.handleCompleteMessage();
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
getPlaywrightReportPath() {
|
|
193
|
-
// default path
|
|
194
|
-
let reportFolder = join(process.cwd(), "playwright-report");
|
|
195
|
-
// check for playwright report path in config file
|
|
196
|
-
// for now, ignore cases of multiple reporters
|
|
197
|
-
const playwrightConfig = require(this.getPlaywrightConfigFile());
|
|
198
|
-
const { reporter } = playwrightConfig;
|
|
199
|
-
if (
|
|
200
|
-
reporter instanceof Array &&
|
|
201
|
-
reporter.length > 1 &&
|
|
202
|
-
reporter[1]?.outputFolder
|
|
203
|
-
) {
|
|
204
|
-
reportFolder = reporter[1]?.outputFolder;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// check for env variable
|
|
208
|
-
if (process.env.PLAYWRIGHT_HTML_REPORT) {
|
|
209
|
-
reportFolder = process.env.PLAYWRIGHT_HTML_REPORT;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return join(reportFolder, "index.html");
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
getPlaywrightConfigFile() {
|
|
216
|
-
return join(this.getRootDirPath(), "playwright.config.ts");
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
startUploadAgent(sessionId: string, uploadURL: string) {
|
|
220
|
-
return new Promise((resolve, reject) => {
|
|
221
|
-
console.log("Starting upload agent");
|
|
222
|
-
this.uploadAgent = childProcess.spawn("node", [
|
|
223
|
-
this.UPLOAD_AGENT_PATH,
|
|
224
|
-
JSON.stringify({
|
|
225
|
-
sessionId,
|
|
226
|
-
checksumApiURL: this.CHECKSUM_API_URL,
|
|
227
|
-
apiKey: this.config.apiKey,
|
|
228
|
-
}),
|
|
229
|
-
...(this.mock ? ["mock"] : []),
|
|
230
|
-
]);
|
|
231
|
-
|
|
232
|
-
// Listen for messages from the upload agent
|
|
233
|
-
this.uploadAgent.stdout.on("data", (data) => {
|
|
234
|
-
const message = data.toString().trim();
|
|
235
|
-
|
|
236
|
-
// if not a formatted message from upload agent - ignore
|
|
237
|
-
if (!message.startsWith("upag:")) {
|
|
238
|
-
// console.log(`Message from upload agent: ${message}`); // remove
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const [key, value] = message.substring(5).split("=");
|
|
243
|
-
if (key === "port") {
|
|
244
|
-
console.log("Received port from upload agent", value);
|
|
245
|
-
resolve(value);
|
|
246
|
-
} else {
|
|
247
|
-
this.handleUploadAgentMessage(key, value);
|
|
248
|
-
}
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
// Handle exit event
|
|
252
|
-
this.uploadAgent.on("exit", (code, signal) => {
|
|
253
|
-
console.log(
|
|
254
|
-
`upload agent process exited with code ${code} and signal ${signal}`
|
|
255
|
-
);
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
// Handle errors
|
|
259
|
-
this.uploadAgent.on("error", (err) => {
|
|
260
|
-
console.error(`Error starting upload agent: ${err.message}`);
|
|
261
|
-
});
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
private async handleUploadAgentMessage(key: any, value: any) {
|
|
266
|
-
switch (key) {
|
|
267
|
-
case "complete":
|
|
268
|
-
// console.log("Received upload complete message from upload agent");
|
|
269
|
-
this.sendUploadsComplete().then(() => {
|
|
270
|
-
this.completeIndicators.upload = true;
|
|
271
|
-
});
|
|
272
|
-
break;
|
|
273
|
-
case "report-uploaded":
|
|
274
|
-
// console.log("Received report uploaded message from upload agent");
|
|
275
|
-
const reportURL = await this.sendTestrunEnd();
|
|
276
|
-
this.completeIndicators.report = true;
|
|
277
|
-
if (reportURL) {
|
|
278
|
-
console.log(
|
|
279
|
-
`*******************\n* Checksum report URL: ${reportURL}\n*******************`
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
break;
|
|
283
|
-
|
|
284
|
-
default:
|
|
285
|
-
console.warn(`Unhandled upload agent message: ${key}=${value}`);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
async handleCompleteMessage() {
|
|
290
|
-
while (true) {
|
|
291
|
-
if (
|
|
292
|
-
Object.keys(this.completeIndicators).find(
|
|
293
|
-
(key) => !this.completeIndicators[key]
|
|
294
|
-
)
|
|
295
|
-
) {
|
|
296
|
-
await this.awaitSleep(1000);
|
|
297
|
-
} else {
|
|
298
|
-
console.log("Tests complete");
|
|
299
|
-
this.shutdown(this.didFail ? 1 : 0);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
shutdown(code = 0) {
|
|
305
|
-
this.cleanup();
|
|
306
|
-
process.exit(code);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
buildVolatileConfig() {
|
|
310
|
-
// if no volatile config set - return
|
|
311
|
-
if (!this.volatileChecksumConfig) {
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
const configPath = this.getVolatileConfigPath();
|
|
316
|
-
const configString = `
|
|
317
|
-
import { RunMode, getChecksumConfig } from "@checksum-ai/runtime";
|
|
318
|
-
|
|
319
|
-
export default getChecksumConfig(${JSON.stringify(this.config, null, 2)});
|
|
320
|
-
`;
|
|
321
|
-
|
|
322
|
-
writeFileSync(configPath, configString);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
cleanup() {
|
|
326
|
-
this.deleteVolatileConfig();
|
|
327
|
-
this.uploadAgent.stdin.write("cli:shutdown");
|
|
328
|
-
this.uploadAgent.kill();
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
async getSession() {
|
|
332
|
-
try {
|
|
333
|
-
if (this.mock) {
|
|
334
|
-
this.testSession = {
|
|
335
|
-
uuid: "session-id-1234",
|
|
336
|
-
uploadURL: "http://localhost:3000/upload",
|
|
337
|
-
};
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
const apiKey = this.config.apiKey;
|
|
342
|
-
if (!apiKey || apiKey === "<API key>") {
|
|
343
|
-
console.error("No API key found in checksum config");
|
|
344
|
-
this.shutdown(1);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
const body = JSON.stringify(await this.getEnvInfo());
|
|
348
|
-
const res = await fetch(`${this.CHECKSUM_API_URL}/client-api/test-runs`, {
|
|
349
|
-
method: "POST",
|
|
350
|
-
headers: {
|
|
351
|
-
Accept: "application/json",
|
|
352
|
-
"Content-Type": "application/json",
|
|
353
|
-
ChecksumAppCode: apiKey,
|
|
354
|
-
},
|
|
355
|
-
body,
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
this.testSession = await res.json();
|
|
359
|
-
} catch (e) {
|
|
360
|
-
console.error("Error getting checksum test session", e);
|
|
361
|
-
this.shutdown(1);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
async sendTestrunEnd() {
|
|
366
|
-
try {
|
|
367
|
-
if (this.mock) {
|
|
368
|
-
return "https://mock.report.url";
|
|
369
|
-
}
|
|
370
|
-
const { uuid: id } = this.testSession;
|
|
371
|
-
const body = JSON.stringify({
|
|
372
|
-
failed: this.didFail ? 1 : 0,
|
|
373
|
-
passed: 0,
|
|
374
|
-
healed: 0,
|
|
375
|
-
endedAt: Date.now(),
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
const { reportURL } = await this.updateTestRun(
|
|
379
|
-
`${this.CHECKSUM_API_URL}/client-api/test-runs/${id}`,
|
|
380
|
-
"PATCH",
|
|
381
|
-
body
|
|
382
|
-
);
|
|
383
|
-
return reportURL;
|
|
384
|
-
} catch (e) {
|
|
385
|
-
console.log("Error sending test run end", e.message);
|
|
386
|
-
return null;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
async sendUploadsComplete() {
|
|
391
|
-
if (this.mock) {
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
try {
|
|
395
|
-
const { uuid: id } = this.testSession;
|
|
396
|
-
|
|
397
|
-
await this.updateTestRun(
|
|
398
|
-
`${this.CHECKSUM_API_URL}/client-api/test-runs/${id}/uploads-completed`,
|
|
399
|
-
"PATCH"
|
|
400
|
-
);
|
|
401
|
-
} catch (e) {
|
|
402
|
-
console.log("Error sending test run uploads complete", e.message);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
/**
|
|
407
|
-
* Sends update to the test run API
|
|
408
|
-
*
|
|
409
|
-
* @param url API endpoint
|
|
410
|
-
* @param method HTTP method
|
|
411
|
-
* @param body request body
|
|
412
|
-
* @returns JSON response from API
|
|
413
|
-
*/
|
|
414
|
-
async updateTestRun(
|
|
415
|
-
url: string,
|
|
416
|
-
method: string,
|
|
417
|
-
body: string | undefined = undefined
|
|
418
|
-
) {
|
|
419
|
-
const res = await fetch(url, {
|
|
420
|
-
method,
|
|
421
|
-
headers: {
|
|
422
|
-
Accept: "application/json",
|
|
423
|
-
"Content-Type": "application/json",
|
|
424
|
-
ChecksumAppCode: this.config.apiKey,
|
|
425
|
-
},
|
|
426
|
-
body,
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
return res.json();
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
async getEnvInfo() {
|
|
433
|
-
const info = {
|
|
434
|
-
commitHash: "",
|
|
435
|
-
branch: "branch",
|
|
436
|
-
environment: process.env.CI ? "CI" : "local",
|
|
437
|
-
name: "name",
|
|
438
|
-
startedAt: Date.now(),
|
|
439
|
-
};
|
|
440
|
-
|
|
441
|
-
try {
|
|
442
|
-
info.commitHash = (await this.getCmdOutput(`git rev-parse HEAD`))
|
|
443
|
-
.toString()
|
|
444
|
-
.trim();
|
|
445
|
-
} catch (e) {
|
|
446
|
-
console.log("Error getting git hash", e.message);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
try {
|
|
450
|
-
info.branch = (await this.getCmdOutput(`git rev-parse --abbrev-ref HEAD`))
|
|
451
|
-
.toString()
|
|
452
|
-
.trim();
|
|
453
|
-
} catch (e) {
|
|
454
|
-
console.log("Error getting branch name", e.message);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
return info;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
getVolatileConfigPath() {
|
|
461
|
-
return join(this.getRootDirPath(), "checksum.config.tmp.ts");
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
deleteVolatileConfig() {
|
|
465
|
-
const configPath = this.getVolatileConfigPath();
|
|
466
|
-
if (existsSync(configPath)) {
|
|
467
|
-
rmSync(configPath);
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
setChecksumConfig() {
|
|
472
|
-
this.config = {
|
|
473
|
-
...(require(join(this.getRootDirPath(), "checksum.config.ts")).default ||
|
|
474
|
-
{}),
|
|
475
|
-
...(this.volatileChecksumConfig || {}),
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
/**
|
|
480
|
-
* Search for checksum config in the command arguments.
|
|
481
|
-
* If found, parse and remove from args.
|
|
482
|
-
*
|
|
483
|
-
* @param args arguments passed to the command
|
|
484
|
-
* @returns args without checksum config
|
|
485
|
-
*/
|
|
486
|
-
getChecksumConfigFromCommand(args) {
|
|
487
|
-
// delete any old config if exists
|
|
488
|
-
this.deleteVolatileConfig();
|
|
489
|
-
|
|
490
|
-
for (const arg of args) {
|
|
491
|
-
if (arg.startsWith("--checksum-config")) {
|
|
492
|
-
try {
|
|
493
|
-
this.volatileChecksumConfig = JSON.parse(arg.split("=")[1]);
|
|
494
|
-
return args.filter((a) => a !== arg);
|
|
495
|
-
} catch (e) {
|
|
496
|
-
console.log("Error parsing checksum config", e.message);
|
|
497
|
-
this.volatileChecksumConfig = undefined;
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
return args;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
install() {
|
|
506
|
-
console.log(
|
|
507
|
-
"Creating Checksum directory and necessary files to run your tests"
|
|
508
|
-
);
|
|
509
|
-
|
|
510
|
-
const checksumRoot = this.getRootDirPath();
|
|
511
|
-
|
|
512
|
-
if (!existsSync(this.getRootDirPath())) {
|
|
513
|
-
mkdirSync(checksumRoot);
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
if (!existsSync(this.getChecksumRootOrigin())) {
|
|
517
|
-
throw new Error(
|
|
518
|
-
"Could not find checksum root directory, please install @checksum-ai/runtime package"
|
|
519
|
-
);
|
|
520
|
-
|
|
521
|
-
// automatically install?
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
// copy sources
|
|
525
|
-
[
|
|
526
|
-
"checksum.config.ts",
|
|
527
|
-
"playwright.config.ts",
|
|
528
|
-
"login.ts",
|
|
529
|
-
"README.md",
|
|
530
|
-
].forEach((file) => {
|
|
531
|
-
copyFileSync(
|
|
532
|
-
join(this.getChecksumRootOrigin(), file),
|
|
533
|
-
join(checksumRoot, file)
|
|
534
|
-
);
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
// create tests folder
|
|
538
|
-
mkdirSync(join(checksumRoot, "tests"), {
|
|
539
|
-
recursive: true,
|
|
540
|
-
});
|
|
541
|
-
|
|
542
|
-
// create test data directories
|
|
543
|
-
["esra", "har", "trace", "log"].forEach((folder) => {
|
|
544
|
-
mkdirSync(join(checksumRoot, "test-data", folder), {
|
|
545
|
-
recursive: true,
|
|
546
|
-
});
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
getRootDirPath() {
|
|
551
|
-
return join(process.cwd(), CHECKSUM_ROOT_FOLDER);
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
getChecksumRootOrigin() {
|
|
555
|
-
return join(
|
|
556
|
-
process.cwd(),
|
|
557
|
-
"node_modules",
|
|
558
|
-
"@checksum-ai",
|
|
559
|
-
"runtime",
|
|
560
|
-
"checksum-root"
|
|
561
|
-
);
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
/**
|
|
565
|
-
* Adds a timeout limit for a promise to resolve
|
|
566
|
-
* Will throw an error if the promise does not resolve within the timeout limit
|
|
567
|
-
*
|
|
568
|
-
* @param promise promise to add timeout to
|
|
569
|
-
* @param timeout timeout in milliseconds
|
|
570
|
-
* @param errMessage error message to throw if timeout is reached
|
|
571
|
-
* @returns promise that resolves if the original promise resolves within the timeout limit
|
|
572
|
-
*/
|
|
573
|
-
guardReturn = async (
|
|
574
|
-
promise,
|
|
575
|
-
timeout = 1_000,
|
|
576
|
-
errMessage = "action hang guard timed out"
|
|
577
|
-
) => {
|
|
578
|
-
const timeoutStringIdentifier = "guard-timed-out";
|
|
579
|
-
const guard = async () => {
|
|
580
|
-
await this.awaitSleep(timeout + 1_000);
|
|
581
|
-
return timeoutStringIdentifier;
|
|
582
|
-
};
|
|
583
|
-
|
|
584
|
-
const res = await Promise.race([promise, guard()]);
|
|
585
|
-
if (typeof res === "string" && res === timeoutStringIdentifier) {
|
|
586
|
-
throw new Error(errMessage);
|
|
587
|
-
}
|
|
588
|
-
return res;
|
|
589
|
-
};
|
|
590
|
-
|
|
591
|
-
awaitSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
/**
|
|
595
|
-
* Trigger the main function
|
|
596
|
-
*/
|
|
597
|
-
(async () => {
|
|
598
|
-
await new ChecksumCLI().execute();
|
|
599
|
-
})();
|
package/types.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { Page } from "@playwright/test";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Checksum runtime root folder
|
|
5
|
-
*/
|
|
6
|
-
export const CHECKSUM_ROOT_FOLDER = "checksum";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Checksum runtime running mode -
|
|
10
|
-
* normal - tests run normally
|
|
11
|
-
* heal - checksum will attempt to heal tests that failed using fallback
|
|
12
|
-
* refactor - checksum will attempt to refactor and improve your tests
|
|
13
|
-
*/
|
|
14
|
-
export enum RunMode {
|
|
15
|
-
Normal = "normal",
|
|
16
|
-
Heal = "heal",
|
|
17
|
-
Refactor = "refactor",
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface IChecksumPage extends Page {
|
|
21
|
-
initWithTest: (testInfo) => Promise<void>;
|
|
22
|
-
checksumId: (id: string) => IChecksumPage;
|
|
23
|
-
checksumStep: (thought: string, testFunction?: () => void) => IChecksumPage;
|
|
24
|
-
testId: (testId: string) => void;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export type RuntimeOptions = {
|
|
28
|
-
/**
|
|
29
|
-
* fallback to ESRA if the action selector is not found
|
|
30
|
-
*/
|
|
31
|
-
actionsESRAfallback: boolean;
|
|
32
|
-
actionsLLMFallback: boolean; // use LLM fallback if action selector is not found
|
|
33
|
-
// actionsChangeSelectors: boolean; // change the selector of the action if found better match
|
|
34
|
-
// existingAssertionsESRAfallback: boolean; // fallback to ESRA if the existing assertion selector is not found
|
|
35
|
-
// existingAssertionsChangeSelectors: boolean; // change the selector of existing assertion if found better match
|
|
36
|
-
newAssertionsEnabled: boolean; // add new assertions
|
|
37
|
-
writeTraceFile: boolean; // saves trace file at the end of the test
|
|
38
|
-
useMockData: boolean; // use mocked data
|
|
39
|
-
printLogs: boolean; // print logs to console
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export type ChecksumConfig = {
|
|
43
|
-
runMode: RunMode;
|
|
44
|
-
apiKey: string;
|
|
45
|
-
baseURL: string;
|
|
46
|
-
username?: string;
|
|
47
|
-
password?: string;
|
|
48
|
-
options?: RuntimeOptions;
|
|
49
|
-
|
|
50
|
-
apiURL: string;
|
|
51
|
-
uploadAgentHOST: string;
|
|
52
|
-
uploadAgentPort: number;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
export type TestSuiteSession = {
|
|
56
|
-
uuid: string;
|
|
57
|
-
uploadURL: string;
|
|
58
|
-
};
|