@leveragent/e2e-testing 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -0
- package/dist/agentRunner-PLMMM56Z.js +38 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +2 -0
- package/package.json +37 -0
- package/prompts/custom-test.md +6 -0
- package/prompts/diff-test.md +6 -0
- package/prompts/init.md +58 -0
- package/prompts/test.md +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Leveragent Add Cypress
|
|
2
|
+
|
|
3
|
+
Install-and-run CLI assistant package that executes a startup skill to set up Cypress/Cucumber style e2e testing in the current repository.
|
|
4
|
+
|
|
5
|
+
## Quick Start (Published Package)
|
|
6
|
+
|
|
7
|
+
Run this in the target repository (the one you want to modify):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @leveragent/add-cypress
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The command runs in your current working directory, so the assistant tools operate on the actual repository where you invoked `npx`.
|
|
14
|
+
|
|
15
|
+
The startup skill markdown is loaded from the package's bundled `skills/` folder.
|
|
16
|
+
|
|
17
|
+
## Prerequisites
|
|
18
|
+
|
|
19
|
+
- GitHub Copilot CLI installed and authenticated
|
|
20
|
+
- Node.js 22+ (required by current Copilot CLI runtime dependencies)
|
|
21
|
+
|
|
22
|
+
Authentication can come from either:
|
|
23
|
+
|
|
24
|
+
- A logged-in Copilot CLI session (`copilot auth login`)
|
|
25
|
+
- A token environment variable (`GITHUB_TOKEN`, `GH_TOKEN`, or `COPILOT_SDK_AUTH_TOKEN`)
|
|
26
|
+
|
|
27
|
+
## Local Development Setup
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
nvm install
|
|
31
|
+
nvm use
|
|
32
|
+
npm install
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Run
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm start
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Then chat in the terminal and use `exit` to quit.
|
|
42
|
+
|
|
43
|
+
If auth is missing or invalid, the app now exits early with a clear message instead of failing after the first prompt.
|
|
44
|
+
|
|
45
|
+
## Commands
|
|
46
|
+
|
|
47
|
+
- `/help` show available commands
|
|
48
|
+
- `/status` show active session + auth status
|
|
49
|
+
- `/models` list first 20 available models
|
|
50
|
+
- `/new` start a fresh session
|
|
51
|
+
- `exit` or `quit` stop the assistant
|
|
52
|
+
|
|
53
|
+
## Optional environment variables
|
|
54
|
+
|
|
55
|
+
- `COPILOT_MODEL` (default: `gpt-5`)
|
|
56
|
+
- `COPILOT_REASONING_EFFORT` (`low` | `medium` | `high` | `xhigh`, default: `high`)
|
|
57
|
+
- `COPILOT_RESUME_LAST` (`true`/`false`, default: `true`)
|
|
58
|
+
- `COPILOT_REQUEST_TIMEOUT_MS` (default: `60000`)
|
|
59
|
+
|
|
60
|
+
When `COPILOT_RESUME_LAST=true`, the app resumes your previous session automatically to preserve context and continuity.
|
|
61
|
+
|
|
62
|
+
If you see timeout errors like `Timeout after 60000ms waiting for session.idle`, increase `COPILOT_REQUEST_TIMEOUT_MS`.
|
|
63
|
+
|
|
64
|
+
## Features
|
|
65
|
+
|
|
66
|
+
- Streaming assistant output
|
|
67
|
+
- Interactive prompt loop
|
|
68
|
+
- Resumable sessions with persistent context
|
|
69
|
+
- Inline agent follow-up questions via `ask_user`
|
|
70
|
+
- Custom `get_weather` tool (mocked weather data)
|
|
71
|
+
- Built-in `list_dir` and `glob_files` tools for repository discovery
|
|
72
|
+
- Built-in `read_file` tool for UTF-8 file reads within the working directory
|
|
73
|
+
- Built-in `write_file` tool for UTF-8 file writes within the working directory
|
|
74
|
+
- Built-in `search_code`, `go_to_definition`, and `find_references` tools for code navigation
|
|
75
|
+
- Built-in `run_command` tool for allowlisted project commands (`npm run check|test|build|lint`, `npm test`)
|
|
76
|
+
- Built-in git context tools: `git_status`, `git_diff`, `git_log`
|
|
77
|
+
- System prompt customization
|
|
78
|
+
|
|
79
|
+
## Built-in tools
|
|
80
|
+
|
|
81
|
+
- `get_weather`
|
|
82
|
+
- Returns mocked weather details for a city.
|
|
83
|
+
- `list_dir`
|
|
84
|
+
- Lists files/directories under a workspace path with depth, hidden-file, and result limits.
|
|
85
|
+
- `glob_files`
|
|
86
|
+
- Finds files using glob patterns (for example `src/**/*.ts`) scoped to the workspace.
|
|
87
|
+
- `read_file`
|
|
88
|
+
- Reads UTF-8 text from a file in the current working directory tree.
|
|
89
|
+
- Rejects paths outside the working directory.
|
|
90
|
+
- Uses a size limit (`maxBytes`, default `200000`) to avoid unexpectedly large reads.
|
|
91
|
+
- `write_file`
|
|
92
|
+
- Writes UTF-8 text to a file in the current working directory tree.
|
|
93
|
+
- Rejects paths outside the working directory.
|
|
94
|
+
- Creates parent directories as needed.
|
|
95
|
+
- Supports `overwrite` (default `true`).
|
|
96
|
+
- `search_code`
|
|
97
|
+
- Searches text or regex patterns across workspace files with optional glob filtering and result caps.
|
|
98
|
+
- `go_to_definition`
|
|
99
|
+
- Heuristically finds likely declaration sites (`function`, `class`, `interface`, `type`, `enum`, variables, methods).
|
|
100
|
+
- `find_references`
|
|
101
|
+
- Finds symbol references across code-like files.
|
|
102
|
+
- `run_command`
|
|
103
|
+
- Executes only allowlisted local commands for validation.
|
|
104
|
+
- Allowed: `npm run check`, `npm run test`, `npm run build`, `npm run lint`, `npm test`.
|
|
105
|
+
- `git_status`
|
|
106
|
+
- Returns `git status --short --branch` output.
|
|
107
|
+
- `git_diff`
|
|
108
|
+
- Returns unstaged or staged diff output, optionally scoped to a file.
|
|
109
|
+
- `git_log`
|
|
110
|
+
- Returns recent commit history in one-line format.
|
|
111
|
+
|
|
112
|
+
## Important note
|
|
113
|
+
|
|
114
|
+
If you see `ERR_UNKNOWN_BUILTIN_MODULE: node:sqlite`, switch to Node 22+:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
nvm use 22
|
|
118
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{CopilotClient as vt}from"@github/copilot-sdk";import*as wt from"readline";import{approveAll as Ut}from"@github/copilot-sdk";import{readFile as bt}from"fs/promises";var Z=(t,e)=>t===void 0?e:/^(1|true|yes|on)$/i.test(t.trim()),tt=t=>{let e=(t??"high").trim().toLowerCase();switch(e){case"low":case"medium":case"high":case"xhigh":return e;default:return"high"}},et=async(t,e={})=>{let{trim:s=!0,onError:o}=e;try{let n=await bt(t,"utf8");return(s?n.trim():n)||""}catch(n){return o?.(n),""}},I=t=>typeof t=="number"&&Number.isFinite(t)?t:0,st=t=>new Intl.NumberFormat("en-US",{style:"currency",currency:"USD",minimumFractionDigits:4,maximumFractionDigits:6}).format(t);var yt="",B="none",ot=`You are a senior frontend engineer, specialized on E2E testing in a local CLI. Be concise but highly useful.
|
|
3
|
+
Prefer concrete steps, ask clarifying questions only when necessary, and use tools when needed.
|
|
4
|
+
If a tool returns uncertain data, state assumptions clearly. ${yt}`,nt=1e9,k=Number(process.env.COPILOT_USD_PER_AIU??"0");var Ot="gpt-5.3-codex";var N=1e6,M="\x1B[0m",D="\x1B[38;5;110m",v=1e4,rt=1e6,it=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".json",".go",".py",".java",".rb",".rs",".php",".swift",".kt",".kts",".cs",".c",".h",".hpp",".cpp",".cc"]);var $=process.env.COPILOT_MODEL??Ot,q=tt(process.env.COPILOT_REASONING_EFFORT),at=Z(process.env.COPILOT_RESUME_LAST,!0),H=process.env.COPILOT_SDK_AUTH_TOKEN??process.env.GITHUB_TOKEN??process.env.GH_TOKEN;var Ct="\x1B[0m",Rt="\x1B[38;5;245m",X=t=>`${Rt}${t}${Ct}`,It=t=>{if(t===void 0)return"{}";try{return JSON.stringify(t,null,2)}catch{return"[unserializable arguments]"}},Lt=(t,e,s)=>{if(s){if(t&&typeof t=="object"){let o=t;if(typeof o.detailedContent=="string")return o.detailedContent;if(typeof o.content=="string")return o.content}return"[no response content]"}if(e&&typeof e=="object"){let o=e;if(typeof o.message=="string")return o.message}return"[tool failed without error details]"},ct=(t,e)=>{if(t.waitingForAssistantOutput){if(t.waitingForAssistantOutput=!1,t.waitingForToolResult){e.start("Tool");return}e.stop(!0)}},lt=(t,e)=>{let{uiState:s,spinner:o}=e,n=[];return n.push(t.on("assistant.message_delta",i=>{ct(s,o),process.stdout.write(i.data.deltaContent)})),n.push(t.on("assistant.message",()=>{ct(s,o)})),n.push(t.on("session.idle",()=>{s.waitingForToolResult=!1,s.waitingForAssistantOutput&&(s.waitingForAssistantOutput=!1,o.stop(!1)),process.stdout.write(`
|
|
5
|
+
`)})),n.push(t.on("session.error",i=>{s.waitingForToolResult=!1,s.waitingForAssistantOutput&&(s.waitingForAssistantOutput=!1,o.stop(!1)),console.error(`
|
|
6
|
+
[session error]`,i.data)})),n.push(t.on("tool.execution_start",i=>{let h=i.data.toolName??"unknown_tool",O=It(i.data.arguments);s.waitingForToolResult=!0,o.stop(!1),process.stdout.write(X(`
|
|
7
|
+
[tool] ${h}
|
|
8
|
+
`)),process.stdout.write(X(`[tool args] ${O}
|
|
9
|
+
`)),o.start(`Tool: ${h}`)})),n.push(t.on("tool.execution_complete",i=>{s.waitingForToolResult=!1;let h=Lt(i.data.result,i.data.error,i.data.success),O=i.data.success?"[tool response]":"[tool error]";if(process.stdout.write(X(`${O} ${h}
|
|
10
|
+
`)),s.waitingForAssistantOutput){o.start("Assistant");return}o.stop(!1)})),n.push(t.on("assistant.usage",i=>{e.usageTotals.events+=1,e.usageTotals.inputTokens+=I(i.data.inputTokens),e.usageTotals.outputTokens+=I(i.data.outputTokens),e.usageTotals.cacheReadTokens+=I(i.data.cacheReadTokens),e.usageTotals.cacheWriteTokens+=I(i.data.cacheWriteTokens),e.usageTotals.totalNanoAiu+=I(i.data.copilotUsage?.totalNanoAiu)})),n};var Pt=async(t,e)=>new Promise(s=>{t.rl.question(`${D}${e}`,o=>{process.stdout.write(M),s(o)})}),z=(t,e)=>{t.detachSessionHandlers.forEach(s=>s()),t.detachSessionHandlers=lt(e,t)},ut=t=>({model:$,reasoningEffort:q,streaming:!0,tools:t.tools,onPermissionRequest:Ut,onUserInputRequest:async e=>{process.stdout.write(`
|
|
11
|
+
Agent asks: ${e.question}
|
|
12
|
+
`),e.choices&&e.choices.length>0&&process.stdout.write(`Choices: ${e.choices.join(" | ")}
|
|
13
|
+
`);let s=await Pt(t,"You (for agent): ");return{answer:s,wasFreeform:!e.choices?.includes(s)}},workingDirectory:process.cwd(),infiniteSessions:{enabled:!0,backgroundCompactionThreshold:.8,bufferExhaustionThreshold:.95},systemMessage:{content:ot}}),pt=async t=>{let e=ut(t);if(at){let o=await t.client.getLastSessionId();if(o)try{let n=await t.client.resumeSession(o,e);return z(t,n),process.stdout.write(`[session] Resumed ${n.sessionId}
|
|
14
|
+
`),n}catch(n){console.error("[session] Resume failed, creating a new session:",n)}}let s=await t.client.createSession(e);return z(t,s),process.stdout.write(`[session] Started ${s.sessionId}
|
|
15
|
+
`),s},dt=async t=>{let e=ut(t),s=t.requireSession(),o=s.sessionId;await s.disconnect();let n=await t.client.createSession(e);return z(t,n),process.stdout.write(`[session] Switched from ${o} to ${n.sessionId}
|
|
16
|
+
`),n};var mt=["|","/","-","\\"],ft=t=>{let e=null,s=0,o="Assistant";return{start:h=>{o=h,t()&&(e||(e=setInterval(()=>{let O=mt[s%mt.length];s+=1,process.stdout.write(`\r${o}: ${O}`)},90)))},stop:(h=!1)=>{e&&(clearInterval(e),e=null),s=0,process.stdout.write("\r\x1B[K"),h&&process.stdout.write(`${o}: `)}}};import{spawn as Ft}from"child_process";import{promises as E}from"fs";import*as u from"path";var kt=t=>t.split(u.sep).join("/"),ht=async t=>{let e=await E.realpath(t),s=a=>{let r=u.relative(e,a);return r.length===0?".":r},o=a=>{let r=u.relative(e,a);if(r.startsWith("..")||u.isAbsolute(r))throw new Error(`Path is outside the working directory: ${a}`)},n=a=>{let r=a.trim();if(!r)throw new Error("Path must be a non-empty string.");let l=u.resolve(e,r);return o(l),l},i=async a=>{let r=n(a),l=await E.realpath(r);return o(l),l},h=async a=>{let r=await i(a),l=await E.stat(r);if(!l.isFile())throw new Error(`Path is not a regular file: ${s(r)}`);return{absolutePath:r,stats:l}},O=async a=>{let r=n(a),l=u.dirname(r);await E.mkdir(l,{recursive:!0});let g=await E.realpath(l);return o(g),r},At=async({command:a,args:r,commandCwd:l,timeoutMs:g,maxOutputBytes:S})=>new Promise((C,T)=>{let d=Ft(a,r,{cwd:l,shell:!1,env:{...process.env,GIT_PAGER:"cat"}}),x="",w="",_=!1,b=!1,m=!1,p,y=(c,f,L)=>{if(c.length>=L)return{text:c,truncated:!0};let U=L-c.length;return f.length<=U?{text:c+f,truncated:!1}:{text:c+f.slice(0,U),truncated:!0}};d.stdout&&d.stdout.on("data",c=>{let f=y(x,c.toString(),S);x=f.text,f.truncated&&(_=!0)}),d.stderr&&d.stderr.on("data",c=>{let f=y(w,c.toString(),S);w=f.text,f.truncated&&(b=!0)}),d.on("error",c=>{p&&clearTimeout(p),T(c)}),p=setTimeout(()=>{m=!0,d.pid&&(process.kill(d.pid,"SIGTERM"),setTimeout(()=>{d.pid&&process.kill(d.pid,"SIGKILL")},1e3))},g),d.on("close",(c,f)=>{p&&clearTimeout(p),C({exitCode:c,signal:f,stdout:x,stderr:w,stdoutTruncated:_,stderrTruncated:b,timedOut:m})})}),J=async({rootDirectory:a,maxEntries:r,includeHidden:l,maxDepth:g,includeDirectories:S,includeFiles:C})=>{let T=[],d=!1,x=async(w,_)=>{if(T.length>=r||T.length>=v){d=!0;return}let b=await E.readdir(w,{withFileTypes:!0});b.sort((m,p)=>m.name.localeCompare(p.name));for(let m of b){if(T.length>=r||T.length>=v){d=!0;return}if(!l&&m.name.startsWith("."))continue;let p=u.join(w,m.name),y=s(p);if(m.isDirectory()){S&&T.push({path:y,type:"directory"}),_<g&&await x(p,_+1);continue}if(m.isFile()){if(C){let c=await E.stat(p);T.push({path:y,type:"file",sizeBytes:c.size})}continue}if(m.isSymbolicLink()){let c=await E.realpath(p);o(c),(C||S)&&T.push({path:y,type:"symlink",target:s(c)})}}};return await x(a,0),{entries:T,truncated:d}},xt=async a=>{let r=await i(a),l=await E.stat(r);if(l.isFile())return{files:[r],truncated:!1};if(l.isDirectory()){let g=await J({rootDirectory:r,maxEntries:v,includeHidden:!1,maxDepth:100,includeDirectories:!1,includeFiles:!0});return{files:g.entries.map(S=>u.resolve(e,S.path)),truncated:g.truncated}}throw new Error(`Path is neither a file nor a directory: ${a}`)};return{workspaceRoot:e,toWorkspacePath:s,resolveExistingPath:i,resolveReadableFile:h,resolveWritableFile:O,executeCommand:At,walkDirectory:J,collectTextMatches:async({query:a,searchPath:r,maxResults:l,caseSensitive:g,useRegex:S,globPattern:C,requireCodeFiles:T})=>{let{files:d,truncated:x}=await xt(r),w=[],_=S?new RegExp(a,g?"g":"gi"):void 0,b=g?a:a.toLowerCase(),m=!1;for(let p of d){if(m)break;let y=u.extname(p).toLowerCase();if(T&&!it.has(y))continue;let c=s(p);if(!u.matchesGlob(kt(c),C))continue;let f=await E.stat(p);if(!f.isFile()||f.size>rt)continue;let L=await E.readFile(p,"utf8");if(L.includes("\0"))continue;let U=L.split(/\r?\n/u);for(let P=0;P<U.length&&!m;P+=1){let R=U[P];if(R.length===0)continue;if(_){_.lastIndex=0;for(let F of R.matchAll(_)){let V=F.index;if(V!==void 0&&(w.push({path:c,line:P+1,column:V+1,preview:R}),w.length>=l)){m=!0;break}}continue}let Q=g?R:R.toLowerCase(),W=0;for(;W<Q.length;){let F=Q.indexOf(b,W);if(F===-1)break;if(w.push({path:c,line:P+1,column:F+1,preview:R}),w.length>=l){m=!0;break}W=F+Math.max(b.length,1)}}}return{matches:w,truncated:x||m}}}};var gt=async t=>{let e=await ht(t);return{tools:[]}};var Tt=t=>{let e=t.usageTotals.inputTokens+t.usageTotals.outputTokens+t.usageTotals.cacheReadTokens+t.usageTotals.cacheWriteTokens,s=t.usageTotals.totalNanoAiu/nt,o=s*k;B!=="none"&&(process.stdout.write(`
|
|
17
|
+
Session usage summary:
|
|
18
|
+
`),process.stdout.write(` Input tokens: ${t.usageTotals.inputTokens}
|
|
19
|
+
`),process.stdout.write(` Output tokens: ${t.usageTotals.outputTokens}
|
|
20
|
+
`),B==="detailed"&&(process.stdout.write(` Usage events: ${t.usageTotals.events}
|
|
21
|
+
`),process.stdout.write(` Total tokens: ${e}
|
|
22
|
+
`),process.stdout.write(` Cache read tokens: ${t.usageTotals.cacheReadTokens}
|
|
23
|
+
`),process.stdout.write(` Cache write tokens: ${t.usageTotals.cacheWriteTokens}
|
|
24
|
+
`),process.stdout.write(` Estimated cost: ${s.toFixed(6)} AIU
|
|
25
|
+
`),k>0?process.stdout.write(` Estimated cost (USD): ${st(o)} (COPILOT_USD_PER_AIU=${k})
|
|
26
|
+
`):process.stdout.write(` Estimated cost (USD): set COPILOT_USD_PER_AIU to enable USD estimate
|
|
27
|
+
`)))};var{tools:Nt}=await gt(process.cwd()),Mt=wt.createInterface({input:process.stdin,output:process.stdout}),j=class{constructor(e){this.client=e;this.spinner=ft(()=>!this.isShuttingDown&&!this.isInputClosed)}session=null;rl=Mt;tools=Nt;detachSessionHandlers=[];isShuttingDown=!1;isInputClosed=!1;uiState={waitingForAssistantOutput:!1,waitingForToolResult:!1};usageTotals={events:0,inputTokens:0,outputTokens:0,cacheReadTokens:0,cacheWriteTokens:0,totalNanoAiu:0};spinner;async init(){let e=await pt(this);this.setSession(e)}setSession(e){this.session=e}requireSession(){if(!this.session)throw new Error("No active session available.");return this.session}async cleanupAndExit(){if(this.isShuttingDown)return;if(this.isShuttingDown=!0,this.uiState.waitingForAssistantOutput=!1,this.uiState.waitingForToolResult=!1,this.spinner.stop(!1),this.detachSessionHandlers.forEach(s=>s()),this.detachSessionHandlers=[],this.session)try{await this.session.disconnect()}catch(s){console.error("[shutdown] session disconnect warning",s)}Tt(this);try{await this.client.stop()}catch(s){console.error("[shutdown] client stop warning",s)}this.rl.close(),process.exitCode=0,setTimeout(()=>{process.exit(0)},25).unref()}listenToShutdownSignals(){this.rl.on("close",()=>{this.isShuttingDown=!0,this.isInputClosed=!0}),process.on("unhandledRejection",e=>{this.isShuttingDown||console.error("[unhandled rejection]",e)}),process.on("SIGINT",async()=>{process.stdout.write(`
|
|
28
|
+
Shutting down...
|
|
29
|
+
`),await this.cleanupAndExit()})}};import{fileURLToPath as Dt}from"url";var Et=async t=>{let e=await t.getAuthStatus();if(!e.isAuthenticated){let s=H?"A token was provided but authentication still failed. Verify your token has Copilot access.":"No token detected. Authenticate with `copilot auth login` (or set GITHUB_TOKEN / GH_TOKEN).";console.error("Copilot authentication is not configured."),e.statusMessage&&console.error(`Status: ${e.statusMessage}`),console.error(s),await t.stop(),process.exit(1)}},St=()=>{console.log("System Commands:"),console.log(" /help Show commands"),console.log(" /status Show auth + session info"),console.log(" /models List available models"),console.log(" /new Start a fresh session"),console.log(` exit|quit|q Leave the agent
|
|
30
|
+
`),console.log("----------------------------------------------------------------"),console.log(`
|
|
31
|
+
Test Generation Commands:`),console.log(" /init Init E2E tests from zero"),console.log(" /custom-tests Add custom E2E test based on your input"),console.log(" /diff-test Add E2E test based on code diff")},_t=()=>{console.log("Copilot SDK Interactive Assistant"),console.log("Type your message and press Enter. Use 'exit', 'quit', or 'q' to quit."),St()},G=async t=>{let e=Dt(new URL(`../prompts/${t}`,import.meta.url));return await et(e,{onError:o=>{console.error(`[prompt] could not read ${e}; continuing without startup prompt`,o)}})},A=t=>{if(!(t.isShuttingDown||t.isInputClosed)){if(process.stdin.readableEnded||process.stdin.destroyed){t.isInputClosed=!0,t.cleanupAndExit();return}try{t.rl.question(`${D}You: `,async e=>{if(process.stdout.write(M),t.isShuttingDown||t.isInputClosed)return;let s=e.trim().toLowerCase();if(s==="exit"||s==="quit"||s==="q"){await t.cleanupAndExit();return}if(s==="/help"){St(),A(t);return}if(s==="/new"){try{t.setSession(await dt(t))}catch(n){console.error("[request failed] could not start a new session",n)}A(t);return}if(s==="/status"){try{let n=await t.client.getAuthStatus(),i=t.requireSession();console.log(`
|
|
32
|
+
Status`),console.log(` Session: ${i.sessionId}`),console.log(` Model: ${$}`),console.log(` Reasoning: ${q}`),console.log(` Request timeout: ${N}ms`),console.log(` Authenticated: ${n.isAuthenticated}`),n.login&&console.log(` Login: ${n.login}`)}catch(n){console.error("[request failed] could not read status",n)}process.stdout.write(`
|
|
33
|
+
`),A(t);return}if(s==="/models"){try{let i=(await t.client.listModels()).slice(0,20);console.log(`
|
|
34
|
+
Available models (first 20):`),i.forEach(h=>{console.log(` - ${h.id}`)})}catch(n){console.error("[request failed] could not list models",n)}process.stdout.write(`
|
|
35
|
+
`),A(t);return}if(!e.trim()){A(t);return}let o=e;if(s==="/init"?o=await G("init.md"):s==="/custom-tests"?o=await G("custom-test.md"):s==="/diff-test"?o=await G("diff-test.md"):s==="/t"&&(o=await G("test.md")),!o.trim()){console.error("[prompt] loaded prompt is empty; request was not sent"),A(t);return}process.stdout.write("Assistant: "),t.uiState.waitingForAssistantOutput=!0,t.spinner.start("Assistant");try{await t.requireSession().sendAndWait({prompt:o},N)}catch(n){t.uiState.waitingForAssistantOutput=!1,t.spinner.stop(!1),n instanceof Error&&n.message.includes("Timeout after")?console.error(`
|
|
36
|
+
[request failed] ${n.message}. Increase COPILOT_REQUEST_TIMEOUT_MS (current: ${N}ms).`):console.error(`
|
|
37
|
+
[request failed]`,n)}process.stdout.write(`
|
|
38
|
+
`),!t.isShuttingDown&&!t.isInputClosed&&A(t)})}catch(e){if(e&&typeof e=="object"&&"code"in e&&e.code==="ERR_USE_AFTER_CLOSE"){t.isInputClosed=!0,t.cleanupAndExit();return}throw e}}};var K=new vt({githubToken:H,useLoggedInUser:!0,autoRestart:!1});await K.start();await Et(K);var Y=new j(K);await Y.init();_t();A(Y);Y.listenToShutdownSignals();
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{existsSync as n}from"fs";import{copyFile as c}from"fs/promises";import{createRequire as a}from"module";import{dirname as p,join as s}from"path";var m=async()=>{let i=a(import.meta.url).resolve("vscode-jsonrpc/package.json"),r=p(i),e=s(r,"node"),t=s(r,"node.js");n(e)||!n(t)||await c(t,e)};try{await m()}catch(o){console.error("[bootstrap warning] could not prepare vscode-jsonrpc compatibility shim",o)}await import("./agentRunner-PLMMM56Z.js");
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@leveragent/e2e-testing",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"e2e-testing": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"prompts",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "rm -rf dist && npx tsup src/cli.ts --format esm --dts --minify",
|
|
16
|
+
"start": "tsx src/agentRunner.ts",
|
|
17
|
+
"dev": "tsx watch src/agentRunner.ts",
|
|
18
|
+
"check": "tsc --noEmit",
|
|
19
|
+
"prepublishOnly": "npm run check && npm run build",
|
|
20
|
+
"patch": "npm version --commit-hooks false --git-tag-version false patch",
|
|
21
|
+
"deploy": "npm run patch && npm run build && npm run publish:pure",
|
|
22
|
+
"publish:pure": "npm publish --access public"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=22"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@github/copilot-sdk": "^0.1.32"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^25.5.0",
|
|
32
|
+
"esbuild": "^0.25.9",
|
|
33
|
+
"tsup": "^8.5.1",
|
|
34
|
+
"tsx": "^4.21.0",
|
|
35
|
+
"typescript": "^5.9.3"
|
|
36
|
+
}
|
|
37
|
+
}
|
package/prompts/init.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: init-e2e-tests
|
|
3
|
+
description: Use this guide to add e2e tests for a new project or app
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Execute the following steps
|
|
7
|
+
|
|
8
|
+
## Store the app type
|
|
9
|
+
|
|
10
|
+
- Check the package.json file and decide about the type of the app: angular, vue or react
|
|
11
|
+
- Create a new file if not present: .leveragent/metadata.json
|
|
12
|
+
- Add (or owerwrite) a "appType" property in metadata.json
|
|
13
|
+
|
|
14
|
+
## Install the test-runner first
|
|
15
|
+
|
|
16
|
+
- Check the package.json file, to decide, whether cypress or playwright is installed.
|
|
17
|
+
- If not, install cypress by running: npm install 'cypress --save-dev'
|
|
18
|
+
- Setup the cypress configs:
|
|
19
|
+
Create a cypress.config.ts file in the root with this content:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
import { defineConfig } from "cypress";
|
|
23
|
+
|
|
24
|
+
export default defineConfig({
|
|
25
|
+
e2e: {
|
|
26
|
+
baseUrl: "http://localhost:4200",
|
|
27
|
+
specPattern: "cypress/e2e/**/*.cy.ts",
|
|
28
|
+
supportFile: false,
|
|
29
|
+
},
|
|
30
|
+
video: false,
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Add these scripts to package.json:
|
|
35
|
+
"e2e": "cypress run --browser electron",
|
|
36
|
+
"e2e:open": "cypress open"
|
|
37
|
+
|
|
38
|
+
- Install cucumber if not available yet and add it to the cypress config.
|
|
39
|
+
- Create a super simple smoke test written in cucumber and implemented in cypress
|
|
40
|
+
- Run the the smoke test headless, if something doesn't work, fix it, then repeat.
|
|
41
|
+
|
|
42
|
+
## Store the test runner
|
|
43
|
+
|
|
44
|
+
- Check the package.json file and decide whether app is using cypress or playwright
|
|
45
|
+
- Create a new file if not present: .leveragent/metadata.json
|
|
46
|
+
- Add (or owerwrite) a "testRunner" property in metadata.json to cypress or playwright
|
|
47
|
+
|
|
48
|
+
## Explore Use-Cases
|
|
49
|
+
|
|
50
|
+
- Explore the main use-cases and write test-scenarios in .leveragent/test-scenarios.md using the gherkin syntax
|
|
51
|
+
- Based on this file, generate the cucumber tests implemented with cypress
|
|
52
|
+
- Run the tests in headless mode, if something doesn't work, fix it, then repeat.
|
|
53
|
+
|
|
54
|
+
## Finalize
|
|
55
|
+
|
|
56
|
+
- Tell the user:
|
|
57
|
+
Congrats, you just installed Cypress and Cucumber 🎉
|
|
58
|
+
And you have X Test Scenarios completing successfully ✅
|
package/prompts/test.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Simply echo "HELLO-BELLO"
|