@taqwright/lime-cli 0.10.2 → 0.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";var R=(e,t)=>()=>{try{return t||e((t={exports:{}}).exports,t),t.exports}catch(n){throw t=0,n}};var qe=R((Ks,Bn)=>{Bn.exports={name:"@taqwright/lime-cli",version:"0.10.2",description:"Thin CLI client for the LIME server \u2014 fire a natural-language QA Agent goal against a connected device and write back a taqwright spec.",license:"SEE LICENSE IN LICENSE",author:"Taqwright",homepage:"https://www.taqwright.ai",bugs:{url:"https://www.taqwright.ai/contact"},keywords:["appium","mobile-testing","e2e","qa","test-automation","taqwright","lime","cli"],bin:{"lime-cli":"dist/index.js"},main:"dist/index.js",files:["dist"],publishConfig:{access:"public"},scripts:{"lime-cli":"node src/index.js",start:"node src/index.js",test:"node --test test/*.test.js",bundle:"esbuild src/index.js --bundle --platform=node --target=node18 --minify --outfile=dist/index.js",prepack:"npm run bundle",prepublishOnly:"npm test"},engines:{node:">=18"},devDependencies:{esbuild:"^0.28.1"}}});var D=R((Ys,Ue)=>{"use strict";var Fn="https://www.taqwright.ai";Ue.exports={POLL_INTERVAL_MS:1e3,POLL_TIMEOUT_MS:18e5,BOOT_POLL_INTERVAL_MS:5e3,BOOT_TIMEOUT_MS:3e5,CONNECT_POLL_INTERVAL_MS:2e3,CONNECT_TIMEOUT_MS:3e5,DEFAULT_SERVER:Fn,HTTP_TIMEOUT_MS:3e5,MAX_RESPONSE_BYTES:52428800}});var ee=R((Hs,He)=>{"use strict";var{URL:Kn}=require("url"),{HTTP_TIMEOUT_MS:Be,MAX_RESPONSE_BYTES:Fe}=D();function Ke(e){let t=String(e||"").replace(/^\[|\]$/g,"").toLowerCase();return t==="localhost"||t==="127.0.0.1"||t==="::1"||t.endsWith(".localhost")}var Yn=["just a moment","attention required! | cloudflare","checking your browser","/cdn-cgi/challenge-platform","cf-error-details","cf-browser-verification","_incapsula_resource"];function Ye(e,t){if(!t||typeof t!="string")return null;let n=t.slice(0,4e3).toLowerCase();return Yn.some(r=>n.includes(r))?`the LIME server is behind a bot challenge that blocked this request (HTTP ${e}). The request never reached LIME \u2014 it was stopped at the edge, so your token and arguments are probably fine. CI runners use datacenter IPs, which Cloudflare's Bot Fight Mode challenges by default; it cannot be excepted with a WAF skip rule, so the server's operator has to turn it off or move to Super Bot Fight Mode (which supports skip rules) for the /api/ paths.`:null}var he=class{constructor(t,n){this.server=t,this.token=n}_url(t){return/^https?:\/\//i.test(t)?t:`${this.server}${t}`}json(t,n,r,i={}){let s=r?JSON.stringify(r):null,o={...i};return s&&(o["Content-Type"]="application/json",o["Content-Length"]=Buffer.byteLength(s)),this._send(t,this._url(n),o,s)}raw(t,n,r,i={}){let s={...i,"Content-Type":"application/octet-stream","Content-Length":r.length};return this._send(t,this._url(n),s,r)}_send(t,n,r,i){let s=new Kn(n);if(s.protocol==="http:"&&!Ke(s.hostname))return Promise.reject(new Error(`refusing to send your LIME_CI_TOKEN over cleartext http to ${s.hostname} \u2014 use https (http is allowed only for localhost).`));let o=s.protocol==="https:"?require("https"):require("http"),a={method:t,hostname:s.hostname,port:s.port||(s.protocol==="https:"?443:80),path:s.pathname+(s.search||""),headers:{...r,Authorization:`Bearer ${this.token}`,Origin:`${s.protocol}//${s.host}`}};return new Promise((c,d)=>{let l=o.request(a,u=>{let p="",g=0;u.setEncoding("utf8"),u.on("data",w=>{if(g+=Buffer.byteLength(w),g>Fe){l.destroy(new Error(`Server response exceeded ${Fe} bytes`));return}p+=w}),u.on("end",()=>{let w;try{w=p?JSON.parse(p):{}}catch{let v=Ye(u.statusCode,p);return d(new Error(v||`Server returned non-JSON (${u.statusCode}): ${p.substring(0,200)}`))}c({status:u.statusCode,body:w})})});l.setTimeout(Be,()=>{l.destroy(new Error(`request timed out after ${Be/1e3}s`))}),l.on("error",d),i&&l.write(i),l.end()})}};function Hn(e,t){if(!e.body||!e.body.success)throw new Error(`${t} \u2014 ${e.body&&e.body.error||"unknown"}`);return e.body}He.exports={isLoopbackHost:Ke,HttpClient:he,expectSuccess:Hn,describeEdgeBlock:Ye}});var M=R((Ws,We)=>{"use strict";var Wn={browserstack:{label:"BrowserStack",envUsername:"BROWSERSTACK_USERNAME",envAccessKey:"BROWSERSTACK_ACCESS_KEY",hubHost:"hub.browserstack.com"},lambdatest:{label:"LambdaTest",envUsername:"LT_USERNAME",envAccessKey:"LT_ACCESS_KEY",hubHost:"mobile-hub.lambdatest.com"}};function Vn({provider:e,platform:t,device:n,os:r,appUrl:i,username:s,accessKey:o,capabilities:a}){let c={connectionMode:"cloud",cloudProvider:e||"browserstack",cloudUsername:s,cloudAccessKey:o,platform:t,deviceName:n,deviceOsVersion:r,cloudAppUrl:i,automationName:t==="ios"?"XCUITest":"UiAutomator2"};return a&&(c.cloudCapabilities=a),c}We.exports={CLOUD_PROVIDERS:Wn,buildCloudSettingsPayload:Vn}});var ge=R((Vs,Ge)=>{"use strict";var te=require("fs"),fe=require("path"),Gn=["taqwright","webdriverio-js","appium-java-junit5","appium-python-pytest","appium-ruby-rspec","appium-csharp-nunit"],Jn={taqwright:"deterministic","webdriverio-js":"gemini","appium-java-junit5":"gemini","appium-python-pytest":"gemini","appium-ruby-rspec":"gemini","appium-csharp-nunit":"gemini"},P={taqwright:"taqwright (native)","webdriverio-js":"WebdriverIO (JavaScript)","appium-java-junit5":"Appium Java (JUnit 5)","appium-python-pytest":"Appium Python (pytest)","appium-ruby-rspec":"Appium Ruby (RSpec)","appium-csharp-nunit":"Appium C# (NUnit)"},Xn=5*1024*1024,me={exists:e=>te.existsSync(e),readText:e=>{try{return te.statSync(e).size>Xn?null:te.readFileSync(e,"utf8")}catch{return null}},list:e=>{try{return te.readdirSync(e)}catch{return[]}}};function zn(e,t){let n=e.readText(fe.join(t,"package.json"));if(!n)return{};try{let r=JSON.parse(n);return{...r.dependencies||{},...r.devDependencies||{}}}catch{return{}}}var Qn=new Set(["node_modules","dist","build","target","bin","obj","vendor","out","coverage"]);function Zn(e,t){let n=new Set([""]),r=[];try{r=e.list(t)}catch{r=[]}for(let i of r)i.startsWith(".")||Qn.has(i)||n.add(i);return[...n]}function er(e,t,n){let r=n?fe.join(t,n):t,i=u=>fe.join(r,u),s=u=>n?`${n}/${u}`:u,o=[],a=zn(e,r),c=[];try{c=e.list(r)}catch{c=[]}(a["@taqwright/taqwright"]||a.taqwright||e.exists(i("taqwright.config.ts"))||e.exists(i("taqwright.config.js")))&&o.push({id:"taqwright",label:P.taqwright,reason:"taqwright dependency/config found",manifest:s("package.json")});let d=c.some(u=>/^wdio\.conf\.(js|ts|mjs|cjs)$/.test(u));(a.webdriverio||a["@wdio/cli"]||d)&&o.push({id:"webdriverio-js",label:P["webdriverio-js"],reason:d?"wdio.conf.* found":"webdriverio dependency found",manifest:s("package.json")});for(let u of["pom.xml","build.gradle","build.gradle.kts"]){let p=e.readText(i(u));if(p&&/io\.appium|java-client/.test(p)){o.push({id:"appium-java-junit5",label:P["appium-java-junit5"],reason:`io.appium:java-client in ${s(u)}`,manifest:s(u)});break}}for(let u of["requirements.txt","pyproject.toml"]){let p=e.readText(i(u));if(p&&/appium-python-client/i.test(p)){o.push({id:"appium-python-pytest",label:P["appium-python-pytest"],reason:`Appium-Python-Client in ${s(u)}`,manifest:s(u)});break}}let l=e.readText(i("Gemfile"));l&&/appium_lib(_core)?/.test(l)&&o.push({id:"appium-ruby-rspec",label:P["appium-ruby-rspec"],reason:"appium_lib in Gemfile",manifest:s("Gemfile")});for(let u of c){if(!/\.csproj$/i.test(u))continue;let p=e.readText(i(u));if(p&&/Appium\.WebDriver/i.test(p)){o.push({id:"appium-csharp-nunit",label:P["appium-csharp-nunit"],reason:`Appium.WebDriver in ${s(u)}`,manifest:s(u)});break}}return o}function Ve(e,t=me){let n=new Map;for(let r of Zn(t,e))for(let i of er(t,e,r))n.has(i.id)||n.set(i.id,i);return[...n.values()]}function tr(e,t=me){let n=Ve(e,t);return n.some(r=>r.id==="taqwright")||n.push({id:"taqwright",label:P.taqwright,reason:"default output"}),n}function nr(e){let{requested:t,configured:n,remembered:r,offeredIds:i,detectedRealIds:s,taqwrightDetected:o,javaSignal:a}=e,c=d=>!!d&&i.includes(d);return c(t)?t:c(n)?n:c(r)&&(s.includes(r)||r==="taqwright"&&o)?r:s.length?s[0]:o?"taqwright":a?"appium-java-junit5":"taqwright"}Ge.exports={FRAMEWORK_IDS:Gn,FRAMEWORK_ENGINES:Jn,FRAMEWORK_LABELS:P,nodeFs:me,detectFrameworks:Ve,rankFrameworks:tr,pickDefaultFramework:nr}});var Se=R((Gs,Ze)=>{"use strict";var we=require("path"),{nodeFs:rr}=ge(),Je={taqwright:"tests","webdriverio-js":"test/specs","appium-java-junit5":"src/test/java","appium-python-pytest":"tests","appium-ruby-rspec":"spec","appium-csharp-nunit":"Tests"},ir="tests",sr={taqwright:".spec.ts","webdriverio-js":".spec.ts","appium-java-junit5":".java","appium-python-pytest":".py","appium-ruby-rspec":"_spec.rb","appium-csharp-nunit":".cs"},or=new Set(["appium-python-pytest","appium-ruby-rspec"]);function ye(e){return Je[e]||ir}function be(e){let t=(e||"").split(/[^A-Za-z0-9]+/).filter(Boolean);return t.length?t.map(n=>/^[A-Z0-9]+$/.test(n)?n[0]+n.slice(1).toLowerCase():n[0].toUpperCase()+n.slice(1)).join(""):""}function Ee(e){return(e||"").replace(/([a-z0-9])([A-Z])/g,"$1_$2").split(/[^A-Za-z0-9]+/).filter(Boolean).join("_").toLowerCase()}function ar(e){return Ee(e).split("_").filter(Boolean).join("-")}var cr=6,lr=new Set(["and","or","the","a","an","to","of","in","on","at","as","with","for","then","into","from","by","that","is"]),dr=/@|\d{6,}/;function ur(e){let t=String(e||"").trim().split(/\s+/).filter(Boolean).filter(n=>!dr.test(n)).slice(0,cr);for(;t.length>1&&lr.has(t[t.length-1].toLowerCase());)t.pop();return be(t.join(" "))||"RecordedTest"}function pr(e){let t=String(e||"").trim();return t?/[A-Za-z]/.test(t)?t.length>80?"Keep the name under 80 characters.":/[/\\]/.test(t)?"Use the destination field for the folder.":null:"Use at least one letter.":"Enter a test name."}function hr(e){if(e==null||e==="")return null;let t=String(e);return t!==t.trim()?"must not have leading or trailing spaces.":t.length>120?"is longer than any key LIME mints (120 characters).":/^[a-z0-9][a-z0-9._/:-]*$/.test(t)?t.includes("..")?'must not contain "..".':null:"is not a scenario key \u2014 expected lower-case letters, digits and . _ - / : only."}function Xe(e,t){let n;return or.has(e)?n=Ee(t):n=be(t),`${n}${sr[e]||""}`}function H(e){return String(e||"").split(/[/\\]+/).filter(t=>t&&t!=="."&&t!=="..").join("/")}function fr(e){if(e==null)return null;let t=String(e);return t.trim()===""?null:/^([A-Za-z]:)?[/\\]/.test(t)?"must be a path inside the project, not an absolute path.":t.split(/[/\\]+/).includes("..")?'must not contain ".." \u2014 the test belongs inside the project.':null}function ve(e){let t=H(e).split("/");return t.pop(),t.join("/")}function ze(e){let t=/(?:^|\/)src\/(?:test|main)\/java\/(.+)$/.exec(H(e));return t&&t[1].split("/").filter(Boolean).join(".")||void 0}function mr(e){let t=H(e),n=t.search(/\/src\/(?:test|main)\/java(?:\/|$)/);return n>0?t.slice(0,n):""}function gr(e){let t=new Map;for(let r of e){let i=ve(r);t.set(i,(t.get(i)||0)+1)}if(!t.size)return null;let n=[...t.entries()].sort((r,i)=>i[1]-r[1]||r[0].localeCompare(i[0]));return{dir:n[0][0],count:n[0][1]}}var Qe={taqwright:{segment:"tests",file:/\.spec\.(ts|js)$/},"webdriverio-js":{segment:"test/specs",file:/\.(ts|js)$/},"appium-java-junit5":{segment:"src/test/java",file:/\.java$/},"appium-python-pytest":{segment:"tests",file:/^(test_.+|.+_test)\.py$/},"appium-ruby-rspec":{segment:"spec",file:/_spec\.rb$/},"appium-csharp-nunit":{segment:"",file:/Tests\.cs$/}},wr=new Set(["node_modules","dist","build","target","bin","obj","vendor","out","coverage",".git"]),yr=2e4;function br(e,t,n=rr){let r=Qe[t];if(!r)return[];let i=[],s=yr,o=a=>{if(s<=0)return;let c=a?we.join(e,a):e,d=[];try{d=n.list(c)}catch{return}for(let l of d){if(s--<=0)return;if(l.startsWith(".")||wr.has(l))continue;let u=a?`${a}/${l}`:l;if(n.isDir?n.isDir(we.join(e,u)):n.list(we.join(e,u)).length>0){o(u);continue}r.file.test(l)&&(r.segment&&!`/${ve(u)}/`.includes(`/${r.segment}/`)||i.push(u))}};return o(""),i.sort()}function Er(e){let{framework:t,testName:n}=e,r=Xe(t,n),i=(a,c,d=0)=>{let l=H(a),u=t==="appium-java-junit5"?ze(l):void 0;return{dir:l,fileName:r,...u?{packageName:u}:{},source:c,neighbourCount:d}};if(e.remembered!=null)return i(e.remembered,"remembered");let s=gr(e.existingTests||[]);if(s&&s.dir)return i(s.dir,"existing",s.count);let o=e.detection&&e.detection.manifest?ve(e.detection.manifest):"";return o?i(`${o}/${ye(t)}`,"module"):i(ye(t),"convention")}Ze.exports={CONVENTIONAL_DIRS:Je,TEST_PATTERNS:Qe,conventionalDir:ye,pascalCase:be,snakeCase:Ee,kebabCase:ar,testNameFromGoal:ur,validateTestName:pr,validateDest:fr,validateScenarioKey:hr,fileNameFor:Xe,normalizeDir:H,javaPackageOf:ze,moduleRootOf:mr,findExistingTests:br,resolveExportTarget:Er}});var rt=R((Js,nt)=>{"use strict";var{CLOUD_PROVIDERS:ne}=M(),{validateScenarioKey:vr}=Se();function Sr(e){let t=Object.create(null);for(let n=0;n<e.length;n++){let r=e[n];if(!r.startsWith("--"))continue;let i=r.slice(2),s=e[n+1];s===void 0||s.startsWith("--")?t[i]=!0:(t[i]=s,n++)}return t}var tt=["export","adapt","verify","check-locators","check-pom","disconnect","report"];function Tr(e){let t=Array.isArray(e)?e:[],n=[];for(let r=0;r<t.length;r+=1){let i=String(t[r]==null?"":t[r]);!i||i.startsWith("-")||(r>0?String(t[r-1]||""):"").startsWith("--")||n.push(i)}return n}function Ar(e){let t=e[0];return t&&tt.includes(t)?{command:t,rest:e.slice(1)}:{command:"run",rest:e}}function Ir(e="taqwright"){return e==="json"?"recorded-steps.json":"RecordedTest.spec.ts"}function kr(e,t=process.env){let n=[],r=!!e["list-devices"],i=!!e["keep-session"],s=!!e["no-boot"],o=e["lime-dir"]===!0?".lime":typeof e["lime-dir"]=="string"&&e["lime-dir"].trim()?e["lime-dir"].trim():void 0,a=e.format?String(e.format).toLowerCase():"taqwright";if(["taqwright","json"].includes(a)||n.push(`--format must be taqwright or json (got "${e.format}")`),r)return{errors:n,listDevices:r,keepSession:i,noBoot:s,format:a,limeDir:o};(!e.goal||typeof e.goal!="string"||!e.goal.trim())&&n.push("--goal is required");let c;e.project!==void 0&&(typeof e.project!="string"||!e.project.trim()?n.push("--project requires a project name"):c=e.project.trim());let d;if(e.scenario!==void 0){let h=typeof e.scenario=="string"?e.scenario.trim():"",E=h?vr(h):"requires a scenario key";E?n.push(`--scenario ${E}`):d=h}let l,u;e["gemini-key"]!==void 0&&(typeof e["gemini-key"]!="string"||!e["gemini-key"].trim()?n.push("--gemini-key requires a value (or set GEMINI_API_KEY)"):(l=e["gemini-key"].trim(),u="flag")),!l&&typeof t.GEMINI_API_KEY=="string"&&t.GEMINI_API_KEY.trim()&&(l=t.GEMINI_API_KEY.trim(),u="env");let p,g;if(e["qwen-key"]!==void 0&&(typeof e["qwen-key"]!="string"||!e["qwen-key"].trim()?n.push("--qwen-key requires a value (or set QWEN_API_KEY)"):(p=e["qwen-key"].trim(),g="flag")),!p&&typeof t.QWEN_API_KEY=="string"&&t.QWEN_API_KEY.trim()&&(p=t.QWEN_API_KEY.trim(),g="env"),e["max-steps"]!==void 0){let h=Number(e["max-steps"]);(!Number.isFinite(h)||h<1||h>200)&&n.push("--max-steps must be a number between 1 and 200")}let w;e.platform&&(w=String(e.platform).toLowerCase(),["android","ios"].includes(w)||n.push(`--platform must be android or ios (got "${e.platform}")`));let A=!!e.cloud,v=typeof e.os=="string"?e.os:void 0,S=typeof e["app-url"]=="string"?e["app-url"]:void 0,I=typeof e.app=="string"?e.app:void 0,T=typeof e.caps=="string"&&e.caps.trim()?e.caps.trim():void 0,m,y,b;if(A&&(m=e.cloud===!0?"browserstack":String(e.cloud).toLowerCase(),ne[m]||n.push(`--cloud must be ${Object.keys(ne).join(" or ")} (got "${e.cloud}")`)),A){(!e.device||typeof e.device!="string")&&n.push('--cloud requires --device (the cloud device name, e.g. "Google Pixel 8")'),w||n.push("--cloud requires --platform (android or ios)"),v||n.push("--cloud requires --os (the device OS version, e.g. 14.0)"),I?n.push("--app is not supported for cloud runs. Upload the build to your provider \u2014 LambdaTest: POST https://manual-api.lambdatest.com/app/upload/realDevice (-F appFile=@<file>), BrowserStack: POST https://api-cloud.browserstack.com/app-automate/upload (-F file=@<file>) \u2014 then pass the returned id with --app-url. (--app is still supported for local runs, where it clean-reinstalls on the agent device.)"):S||n.push("--cloud requires --app-url <bs://\u2026 | lt://\u2026> (the id from your provider\u2019s app upload API)"),e["app-url"]!==void 0&&(typeof e["app-url"]!="string"||!e["app-url"].trim())&&n.push("--app-url requires a bs:// or lt:// app id"),S&&ne[m]&&(m==="browserstack"&&S.startsWith("lt://")?n.push(`--app-url "${S}" is a LambdaTest app id, but the provider is browserstack`):m==="lambdatest"&&S.startsWith("bs://")&&n.push(`--app-url "${S}" is a BrowserStack app id, but the provider is lambdatest`)),e["cloud-username"]!==void 0&&(typeof e["cloud-username"]!="string"||!e["cloud-username"].trim())&&n.push("--cloud-username requires a value"),e["cloud-access-key"]!==void 0&&(typeof e["cloud-access-key"]!="string"||!e["cloud-access-key"].trim())&&n.push("--cloud-access-key requires a value"),e.caps!==void 0&&(typeof e.caps!="string"||!e.caps.trim())&&n.push("--caps requires a path to a JSON file");let h=ne[m];h&&(y=typeof e["cloud-username"]=="string"&&e["cloud-username"].trim()||t[h.envUsername]||void 0,b=typeof e["cloud-access-key"]=="string"&&e["cloud-access-key"].trim()||t[h.envAccessKey]||void 0,y||n.push(`cloud runs require a username \u2014 pass --cloud-username or set ${h.envUsername} (credentials are never read from your LIME account for CLI runs)`),b||n.push(`cloud runs require an access key \u2014 pass --cloud-access-key or set ${h.envAccessKey}`))}else e.app!==void 0&&(typeof e.app!="string"||!e.app.trim()?n.push("--app requires a path to an APK"):e.device?w&&w!=="android"&&n.push("--app is Android only"):n.push("--app requires --device (lime-cli must connect to a device to reinstall the app)")),e["app-url"]!==void 0&&n.push("--app-url requires --cloud"),e.os!==void 0&&n.push("--os requires --cloud"),e["cloud-username"]!==void 0&&n.push("--cloud-username requires --cloud"),e["cloud-access-key"]!==void 0&&n.push("--cloud-access-key requires --cloud"),e.caps!==void 0&&n.push("--caps requires --cloud");return{errors:n,listDevices:r,keepSession:i,noBoot:s,limeDir:o,device:e.device,platform:w,projectName:c,scenarioKey:d,geminiKey:l,geminiKeySource:u,qwenKey:p,qwenKeySource:g,app:I,cloud:A,cloudProvider:m,cloudUsername:y,cloudAccessKey:b,os:v,appUrl:S,capsPath:T,format:a}}var et=["browserstack","lambdatest","appium"];function _r(e){let t=[],n=[];for(let[r,i]of Object.entries(e||{}))!i||typeof i!="object"||Array.isArray(i)?t.push(`--caps block "${r}" must be a JSON object, e.g. { "${r}": { "someCap": true } }`):et.includes(r)||n.push(`--caps block "${r}" is not one of ${et.join("/")} \u2014 it will be ignored (typo?)`);return{errors:t,warnings:n}}nt.exports={parseArgs:Sr,parseCommand:Ar,positionalPaths:Tr,validateArgs:kr,extToFilename:Ir,validateCapsObject:_r,COMMANDS:tt}});var ot=R((Xs,st)=>{"use strict";var{DEFAULT_SERVER:Rr}=D(),it={export:`lime-cli export \u2014 work out what this repo is, and where the test belongs.
|
|
2
|
+
"use strict";var R=(e,t)=>()=>{try{return t||e((t={exports:{}}).exports,t),t.exports}catch(n){throw t=0,n}};var qe=R((Ks,Bn)=>{Bn.exports={name:"@taqwright/lime-cli",version:"0.10.4",description:"Thin CLI client for the LIME server \u2014 fire a natural-language QA Agent goal against a connected device and write back a taqwright spec.",license:"SEE LICENSE IN LICENSE",author:"Taqwright",homepage:"https://www.taqwright.ai",bugs:{url:"https://www.taqwright.ai/contact"},keywords:["appium","mobile-testing","e2e","qa","test-automation","taqwright","lime","cli"],bin:{"lime-cli":"dist/index.js"},main:"dist/index.js",files:["dist"],publishConfig:{access:"public"},scripts:{"lime-cli":"node src/index.js",start:"node src/index.js",test:"node --test test/*.test.js",bundle:"esbuild src/index.js --bundle --platform=node --target=node18 --minify --outfile=dist/index.js",prepack:"npm run bundle",prepublishOnly:"npm test"},engines:{node:">=18"},devDependencies:{esbuild:"^0.28.1"}}});var D=R((Ys,Ue)=>{"use strict";var Fn="https://www.taqwright.ai";Ue.exports={POLL_INTERVAL_MS:1e3,POLL_TIMEOUT_MS:18e5,BOOT_POLL_INTERVAL_MS:5e3,BOOT_TIMEOUT_MS:3e5,CONNECT_POLL_INTERVAL_MS:2e3,CONNECT_TIMEOUT_MS:3e5,DEFAULT_SERVER:Fn,HTTP_TIMEOUT_MS:3e5,MAX_RESPONSE_BYTES:52428800}});var ee=R((Hs,He)=>{"use strict";var{URL:Kn}=require("url"),{HTTP_TIMEOUT_MS:Be,MAX_RESPONSE_BYTES:Fe}=D();function Ke(e){let t=String(e||"").replace(/^\[|\]$/g,"").toLowerCase();return t==="localhost"||t==="127.0.0.1"||t==="::1"||t.endsWith(".localhost")}var Yn=["just a moment","attention required! | cloudflare","checking your browser","/cdn-cgi/challenge-platform","cf-error-details","cf-browser-verification","_incapsula_resource"];function Ye(e,t){if(!t||typeof t!="string")return null;let n=t.slice(0,4e3).toLowerCase();return Yn.some(r=>n.includes(r))?`the LIME server is behind a bot challenge that blocked this request (HTTP ${e}). The request never reached LIME \u2014 it was stopped at the edge, so your token and arguments are probably fine. CI runners use datacenter IPs, which Cloudflare's Bot Fight Mode challenges by default; it cannot be excepted with a WAF skip rule, so the server's operator has to turn it off or move to Super Bot Fight Mode (which supports skip rules) for the /api/ paths.`:null}var he=class{constructor(t,n){this.server=t,this.token=n}_url(t){return/^https?:\/\//i.test(t)?t:`${this.server}${t}`}json(t,n,r,i={}){let s=r?JSON.stringify(r):null,o={...i};return s&&(o["Content-Type"]="application/json",o["Content-Length"]=Buffer.byteLength(s)),this._send(t,this._url(n),o,s)}raw(t,n,r,i={}){let s={...i,"Content-Type":"application/octet-stream","Content-Length":r.length};return this._send(t,this._url(n),s,r)}_send(t,n,r,i){let s=new Kn(n);if(s.protocol==="http:"&&!Ke(s.hostname))return Promise.reject(new Error(`refusing to send your LIME_CI_TOKEN over cleartext http to ${s.hostname} \u2014 use https (http is allowed only for localhost).`));let o=s.protocol==="https:"?require("https"):require("http"),a={method:t,hostname:s.hostname,port:s.port||(s.protocol==="https:"?443:80),path:s.pathname+(s.search||""),headers:{...r,Authorization:`Bearer ${this.token}`,Origin:`${s.protocol}//${s.host}`}};return new Promise((c,d)=>{let l=o.request(a,u=>{let p="",g=0;u.setEncoding("utf8"),u.on("data",w=>{if(g+=Buffer.byteLength(w),g>Fe){l.destroy(new Error(`Server response exceeded ${Fe} bytes`));return}p+=w}),u.on("end",()=>{let w;try{w=p?JSON.parse(p):{}}catch{let v=Ye(u.statusCode,p);return d(new Error(v||`Server returned non-JSON (${u.statusCode}): ${p.substring(0,200)}`))}c({status:u.statusCode,body:w})})});l.setTimeout(Be,()=>{l.destroy(new Error(`request timed out after ${Be/1e3}s`))}),l.on("error",d),i&&l.write(i),l.end()})}};function Hn(e,t){if(!e.body||!e.body.success)throw new Error(`${t} \u2014 ${e.body&&e.body.error||"unknown"}`);return e.body}He.exports={isLoopbackHost:Ke,HttpClient:he,expectSuccess:Hn,describeEdgeBlock:Ye}});var M=R((Ws,We)=>{"use strict";var Wn={browserstack:{label:"BrowserStack",envUsername:"BROWSERSTACK_USERNAME",envAccessKey:"BROWSERSTACK_ACCESS_KEY",hubHost:"hub.browserstack.com"},lambdatest:{label:"LambdaTest",envUsername:"LT_USERNAME",envAccessKey:"LT_ACCESS_KEY",hubHost:"mobile-hub.lambdatest.com"}};function Vn({provider:e,platform:t,device:n,os:r,appUrl:i,username:s,accessKey:o,capabilities:a}){let c={connectionMode:"cloud",cloudProvider:e||"browserstack",cloudUsername:s,cloudAccessKey:o,platform:t,deviceName:n,deviceOsVersion:r,cloudAppUrl:i,automationName:t==="ios"?"XCUITest":"UiAutomator2"};return a&&(c.cloudCapabilities=a),c}We.exports={CLOUD_PROVIDERS:Wn,buildCloudSettingsPayload:Vn}});var ge=R((Vs,Ge)=>{"use strict";var te=require("fs"),fe=require("path"),Gn=["taqwright","webdriverio-js","appium-java-junit5","appium-python-pytest","appium-ruby-rspec","appium-csharp-nunit"],Jn={taqwright:"deterministic","webdriverio-js":"gemini","appium-java-junit5":"gemini","appium-python-pytest":"gemini","appium-ruby-rspec":"gemini","appium-csharp-nunit":"gemini"},P={taqwright:"taqwright (native)","webdriverio-js":"WebdriverIO (JavaScript)","appium-java-junit5":"Appium Java (JUnit 5)","appium-python-pytest":"Appium Python (pytest)","appium-ruby-rspec":"Appium Ruby (RSpec)","appium-csharp-nunit":"Appium C# (NUnit)"},Xn=5*1024*1024,me={exists:e=>te.existsSync(e),readText:e=>{try{return te.statSync(e).size>Xn?null:te.readFileSync(e,"utf8")}catch{return null}},list:e=>{try{return te.readdirSync(e)}catch{return[]}}};function zn(e,t){let n=e.readText(fe.join(t,"package.json"));if(!n)return{};try{let r=JSON.parse(n);return{...r.dependencies||{},...r.devDependencies||{}}}catch{return{}}}var Qn=new Set(["node_modules","dist","build","target","bin","obj","vendor","out","coverage"]);function Zn(e,t){let n=new Set([""]),r=[];try{r=e.list(t)}catch{r=[]}for(let i of r)i.startsWith(".")||Qn.has(i)||n.add(i);return[...n]}function er(e,t,n){let r=n?fe.join(t,n):t,i=u=>fe.join(r,u),s=u=>n?`${n}/${u}`:u,o=[],a=zn(e,r),c=[];try{c=e.list(r)}catch{c=[]}(a["@taqwright/taqwright"]||a.taqwright||e.exists(i("taqwright.config.ts"))||e.exists(i("taqwright.config.js")))&&o.push({id:"taqwright",label:P.taqwright,reason:"taqwright dependency/config found",manifest:s("package.json")});let d=c.some(u=>/^wdio\.conf\.(js|ts|mjs|cjs)$/.test(u));(a.webdriverio||a["@wdio/cli"]||d)&&o.push({id:"webdriverio-js",label:P["webdriverio-js"],reason:d?"wdio.conf.* found":"webdriverio dependency found",manifest:s("package.json")});for(let u of["pom.xml","build.gradle","build.gradle.kts"]){let p=e.readText(i(u));if(p&&/io\.appium|java-client/.test(p)){o.push({id:"appium-java-junit5",label:P["appium-java-junit5"],reason:`io.appium:java-client in ${s(u)}`,manifest:s(u)});break}}for(let u of["requirements.txt","pyproject.toml"]){let p=e.readText(i(u));if(p&&/appium-python-client/i.test(p)){o.push({id:"appium-python-pytest",label:P["appium-python-pytest"],reason:`Appium-Python-Client in ${s(u)}`,manifest:s(u)});break}}let l=e.readText(i("Gemfile"));l&&/appium_lib(_core)?/.test(l)&&o.push({id:"appium-ruby-rspec",label:P["appium-ruby-rspec"],reason:"appium_lib in Gemfile",manifest:s("Gemfile")});for(let u of c){if(!/\.csproj$/i.test(u))continue;let p=e.readText(i(u));if(p&&/Appium\.WebDriver/i.test(p)){o.push({id:"appium-csharp-nunit",label:P["appium-csharp-nunit"],reason:`Appium.WebDriver in ${s(u)}`,manifest:s(u)});break}}return o}function Ve(e,t=me){let n=new Map;for(let r of Zn(t,e))for(let i of er(t,e,r))n.has(i.id)||n.set(i.id,i);return[...n.values()]}function tr(e,t=me){let n=Ve(e,t);return n.some(r=>r.id==="taqwright")||n.push({id:"taqwright",label:P.taqwright,reason:"default output"}),n}function nr(e){let{requested:t,configured:n,remembered:r,offeredIds:i,detectedRealIds:s,taqwrightDetected:o,javaSignal:a}=e,c=d=>!!d&&i.includes(d);return c(t)?t:c(n)?n:c(r)&&(s.includes(r)||r==="taqwright"&&o)?r:s.length?s[0]:o?"taqwright":a?"appium-java-junit5":"taqwright"}Ge.exports={FRAMEWORK_IDS:Gn,FRAMEWORK_ENGINES:Jn,FRAMEWORK_LABELS:P,nodeFs:me,detectFrameworks:Ve,rankFrameworks:tr,pickDefaultFramework:nr}});var Se=R((Gs,Ze)=>{"use strict";var we=require("path"),{nodeFs:rr}=ge(),Je={taqwright:"tests","webdriverio-js":"test/specs","appium-java-junit5":"src/test/java","appium-python-pytest":"tests","appium-ruby-rspec":"spec","appium-csharp-nunit":"Tests"},ir="tests",sr={taqwright:".spec.ts","webdriverio-js":".spec.ts","appium-java-junit5":".java","appium-python-pytest":".py","appium-ruby-rspec":"_spec.rb","appium-csharp-nunit":".cs"},or=new Set(["appium-python-pytest","appium-ruby-rspec"]);function ye(e){return Je[e]||ir}function be(e){let t=(e||"").split(/[^A-Za-z0-9]+/).filter(Boolean);return t.length?t.map(n=>/^[A-Z0-9]+$/.test(n)?n[0]+n.slice(1).toLowerCase():n[0].toUpperCase()+n.slice(1)).join(""):""}function Ee(e){return(e||"").replace(/([a-z0-9])([A-Z])/g,"$1_$2").split(/[^A-Za-z0-9]+/).filter(Boolean).join("_").toLowerCase()}function ar(e){return Ee(e).split("_").filter(Boolean).join("-")}var cr=6,lr=new Set(["and","or","the","a","an","to","of","in","on","at","as","with","for","then","into","from","by","that","is"]),dr=/@|\d{6,}/;function ur(e){let t=String(e||"").trim().split(/\s+/).filter(Boolean).filter(n=>!dr.test(n)).slice(0,cr);for(;t.length>1&&lr.has(t[t.length-1].toLowerCase());)t.pop();return be(t.join(" "))||"RecordedTest"}function pr(e){let t=String(e||"").trim();return t?/[A-Za-z]/.test(t)?t.length>80?"Keep the name under 80 characters.":/[/\\]/.test(t)?"Use the destination field for the folder.":null:"Use at least one letter.":"Enter a test name."}function hr(e){if(e==null||e==="")return null;let t=String(e);return t!==t.trim()?"must not have leading or trailing spaces.":t.length>120?"is longer than any key LIME mints (120 characters).":/^[a-z0-9][a-z0-9._/:-]*$/.test(t)?t.includes("..")?'must not contain "..".':null:"is not a scenario key \u2014 expected lower-case letters, digits and . _ - / : only."}function Xe(e,t){let n;return or.has(e)?n=Ee(t):n=be(t),`${n}${sr[e]||""}`}function H(e){return String(e||"").split(/[/\\]+/).filter(t=>t&&t!=="."&&t!=="..").join("/")}function fr(e){if(e==null)return null;let t=String(e);return t.trim()===""?null:/^([A-Za-z]:)?[/\\]/.test(t)?"must be a path inside the project, not an absolute path.":t.split(/[/\\]+/).includes("..")?'must not contain ".." \u2014 the test belongs inside the project.':null}function ve(e){let t=H(e).split("/");return t.pop(),t.join("/")}function ze(e){let t=/(?:^|\/)src\/(?:test|main)\/java\/(.+)$/.exec(H(e));return t&&t[1].split("/").filter(Boolean).join(".")||void 0}function mr(e){let t=H(e),n=t.search(/\/src\/(?:test|main)\/java(?:\/|$)/);return n>0?t.slice(0,n):""}function gr(e){let t=new Map;for(let r of e){let i=ve(r);t.set(i,(t.get(i)||0)+1)}if(!t.size)return null;let n=[...t.entries()].sort((r,i)=>i[1]-r[1]||r[0].localeCompare(i[0]));return{dir:n[0][0],count:n[0][1]}}var Qe={taqwright:{segment:"tests",file:/\.spec\.(ts|js)$/},"webdriverio-js":{segment:"test/specs",file:/\.(ts|js)$/},"appium-java-junit5":{segment:"src/test/java",file:/\.java$/},"appium-python-pytest":{segment:"tests",file:/^(test_.+|.+_test)\.py$/},"appium-ruby-rspec":{segment:"spec",file:/_spec\.rb$/},"appium-csharp-nunit":{segment:"",file:/Tests\.cs$/}},wr=new Set(["node_modules","dist","build","target","bin","obj","vendor","out","coverage",".git"]),yr=2e4;function br(e,t,n=rr){let r=Qe[t];if(!r)return[];let i=[],s=yr,o=a=>{if(s<=0)return;let c=a?we.join(e,a):e,d=[];try{d=n.list(c)}catch{return}for(let l of d){if(s--<=0)return;if(l.startsWith(".")||wr.has(l))continue;let u=a?`${a}/${l}`:l;if(n.isDir?n.isDir(we.join(e,u)):n.list(we.join(e,u)).length>0){o(u);continue}r.file.test(l)&&(r.segment&&!`/${ve(u)}/`.includes(`/${r.segment}/`)||i.push(u))}};return o(""),i.sort()}function Er(e){let{framework:t,testName:n}=e,r=Xe(t,n),i=(a,c,d=0)=>{let l=H(a),u=t==="appium-java-junit5"?ze(l):void 0;return{dir:l,fileName:r,...u?{packageName:u}:{},source:c,neighbourCount:d}};if(e.remembered!=null)return i(e.remembered,"remembered");let s=gr(e.existingTests||[]);if(s&&s.dir)return i(s.dir,"existing",s.count);let o=e.detection&&e.detection.manifest?ve(e.detection.manifest):"";return o?i(`${o}/${ye(t)}`,"module"):i(ye(t),"convention")}Ze.exports={CONVENTIONAL_DIRS:Je,TEST_PATTERNS:Qe,conventionalDir:ye,pascalCase:be,snakeCase:Ee,kebabCase:ar,testNameFromGoal:ur,validateTestName:pr,validateDest:fr,validateScenarioKey:hr,fileNameFor:Xe,normalizeDir:H,javaPackageOf:ze,moduleRootOf:mr,findExistingTests:br,resolveExportTarget:Er}});var rt=R((Js,nt)=>{"use strict";var{CLOUD_PROVIDERS:ne}=M(),{validateScenarioKey:vr}=Se();function Sr(e){let t=Object.create(null);for(let n=0;n<e.length;n++){let r=e[n];if(!r.startsWith("--"))continue;let i=r.slice(2),s=e[n+1];s===void 0||s.startsWith("--")?t[i]=!0:(t[i]=s,n++)}return t}var tt=["export","adapt","verify","check-locators","check-pom","disconnect","report"];function Tr(e){let t=Array.isArray(e)?e:[],n=[];for(let r=0;r<t.length;r+=1){let i=String(t[r]==null?"":t[r]);!i||i.startsWith("-")||(r>0?String(t[r-1]||""):"").startsWith("--")||n.push(i)}return n}function Ar(e){let t=e[0];return t&&tt.includes(t)?{command:t,rest:e.slice(1)}:{command:"run",rest:e}}function Ir(e="taqwright"){return e==="json"?"recorded-steps.json":"RecordedTest.spec.ts"}function kr(e,t=process.env){let n=[],r=!!e["list-devices"],i=!!e["keep-session"],s=!!e["no-boot"],o=e["lime-dir"]===!0?".lime":typeof e["lime-dir"]=="string"&&e["lime-dir"].trim()?e["lime-dir"].trim():void 0,a=e.format?String(e.format).toLowerCase():"taqwright";if(["taqwright","json"].includes(a)||n.push(`--format must be taqwright or json (got "${e.format}")`),r)return{errors:n,listDevices:r,keepSession:i,noBoot:s,format:a,limeDir:o};(!e.goal||typeof e.goal!="string"||!e.goal.trim())&&n.push("--goal is required");let c;e.project!==void 0&&(typeof e.project!="string"||!e.project.trim()?n.push("--project requires a project name"):c=e.project.trim());let d;if(e.scenario!==void 0){let h=typeof e.scenario=="string"?e.scenario.trim():"",E=h?vr(h):"requires a scenario key";E?n.push(`--scenario ${E}`):d=h}let l,u;e["gemini-key"]!==void 0&&(typeof e["gemini-key"]!="string"||!e["gemini-key"].trim()?n.push("--gemini-key requires a value (or set GEMINI_API_KEY)"):(l=e["gemini-key"].trim(),u="flag")),!l&&typeof t.GEMINI_API_KEY=="string"&&t.GEMINI_API_KEY.trim()&&(l=t.GEMINI_API_KEY.trim(),u="env");let p,g;if(e["qwen-key"]!==void 0&&(typeof e["qwen-key"]!="string"||!e["qwen-key"].trim()?n.push("--qwen-key requires a value (or set QWEN_API_KEY)"):(p=e["qwen-key"].trim(),g="flag")),!p&&typeof t.QWEN_API_KEY=="string"&&t.QWEN_API_KEY.trim()&&(p=t.QWEN_API_KEY.trim(),g="env"),e["max-steps"]!==void 0){let h=Number(e["max-steps"]);(!Number.isFinite(h)||h<1||h>200)&&n.push("--max-steps must be a number between 1 and 200")}let w;e.platform&&(w=String(e.platform).toLowerCase(),["android","ios"].includes(w)||n.push(`--platform must be android or ios (got "${e.platform}")`));let A=!!e.cloud,v=typeof e.os=="string"?e.os:void 0,S=typeof e["app-url"]=="string"?e["app-url"]:void 0,I=typeof e.app=="string"?e.app:void 0,T=typeof e.caps=="string"&&e.caps.trim()?e.caps.trim():void 0,m,y,b;if(A&&(m=e.cloud===!0?"browserstack":String(e.cloud).toLowerCase(),ne[m]||n.push(`--cloud must be ${Object.keys(ne).join(" or ")} (got "${e.cloud}")`)),A){(!e.device||typeof e.device!="string")&&n.push('--cloud requires --device (the cloud device name, e.g. "Google Pixel 8")'),w||n.push("--cloud requires --platform (android or ios)"),v||n.push("--cloud requires --os (the device OS version, e.g. 14.0)"),I?n.push("--app is not supported for cloud runs. Upload the build to your provider \u2014 LambdaTest: POST https://manual-api.lambdatest.com/app/upload/realDevice (-F appFile=@<file>), BrowserStack: POST https://api-cloud.browserstack.com/app-automate/upload (-F file=@<file>) \u2014 then pass the returned id with --app-url. (--app is still supported for local runs, where it clean-reinstalls on the agent device.)"):S||n.push("--cloud requires --app-url <bs://\u2026 | lt://\u2026> (the id from your provider\u2019s app upload API)"),e["app-url"]!==void 0&&(typeof e["app-url"]!="string"||!e["app-url"].trim())&&n.push("--app-url requires a bs:// or lt:// app id"),S&&ne[m]&&(m==="browserstack"&&S.startsWith("lt://")?n.push(`--app-url "${S}" is a LambdaTest app id, but the provider is browserstack`):m==="lambdatest"&&S.startsWith("bs://")&&n.push(`--app-url "${S}" is a BrowserStack app id, but the provider is lambdatest`)),e["cloud-username"]!==void 0&&(typeof e["cloud-username"]!="string"||!e["cloud-username"].trim())&&n.push("--cloud-username requires a value"),e["cloud-access-key"]!==void 0&&(typeof e["cloud-access-key"]!="string"||!e["cloud-access-key"].trim())&&n.push("--cloud-access-key requires a value"),e.caps!==void 0&&(typeof e.caps!="string"||!e.caps.trim())&&n.push("--caps requires a path to a JSON file");let h=ne[m];h&&(y=typeof e["cloud-username"]=="string"&&e["cloud-username"].trim()||t[h.envUsername]||void 0,b=typeof e["cloud-access-key"]=="string"&&e["cloud-access-key"].trim()||t[h.envAccessKey]||void 0,y||n.push(`cloud runs require a username \u2014 pass --cloud-username or set ${h.envUsername} (credentials are never read from your LIME account for CLI runs)`),b||n.push(`cloud runs require an access key \u2014 pass --cloud-access-key or set ${h.envAccessKey}`))}else e.app!==void 0&&(typeof e.app!="string"||!e.app.trim()?n.push("--app requires a path to an APK"):e.device?w&&w!=="android"&&n.push("--app is Android only"):n.push("--app requires --device (lime-cli must connect to a device to reinstall the app)")),e["app-url"]!==void 0&&n.push("--app-url requires --cloud"),e.os!==void 0&&n.push("--os requires --cloud"),e["cloud-username"]!==void 0&&n.push("--cloud-username requires --cloud"),e["cloud-access-key"]!==void 0&&n.push("--cloud-access-key requires --cloud"),e.caps!==void 0&&n.push("--caps requires --cloud");return{errors:n,listDevices:r,keepSession:i,noBoot:s,limeDir:o,device:e.device,platform:w,projectName:c,scenarioKey:d,geminiKey:l,geminiKeySource:u,qwenKey:p,qwenKeySource:g,app:I,cloud:A,cloudProvider:m,cloudUsername:y,cloudAccessKey:b,os:v,appUrl:S,capsPath:T,format:a}}var et=["browserstack","lambdatest","appium"];function _r(e){let t=[],n=[];for(let[r,i]of Object.entries(e||{}))!i||typeof i!="object"||Array.isArray(i)?t.push(`--caps block "${r}" must be a JSON object, e.g. { "${r}": { "someCap": true } }`):et.includes(r)||n.push(`--caps block "${r}" is not one of ${et.join("/")} \u2014 it will be ignored (typo?)`);return{errors:t,warnings:n}}nt.exports={parseArgs:Sr,parseCommand:Ar,positionalPaths:Tr,validateArgs:kr,extToFilename:Ir,validateCapsObject:_r,COMMANDS:tt}});var ot=R((Xs,st)=>{"use strict";var{DEFAULT_SERVER:Rr}=D(),it={export:`lime-cli export \u2014 work out what this repo is, and where the test belongs.
|
|
3
3
|
|
|
4
4
|
Detects the test framework from the project's own manifests (pytest, JUnit, RSpec, WebdriverIO,
|
|
5
5
|
C#, taqwright), picks the directory its existing tests live in, and writes the adaptation brief.
|
|
@@ -373,9 +373,9 @@ ${dt(e)}`);if(n&&o.platform!==n)throw new Error(`Device "${t}" matched on ${o.pl
|
|
|
373
373
|
-android uiautomator \u2192 getByUiSelector \xB7 -ios class chain \u2192 getByClassChain
|
|
374
374
|
-ios predicate string \u2192 getByPredicate`:` accessibility id \u2192 your client's ACCESSIBILITY_ID lookup \xB7 xpath \u2192 XPATH \xB7 id \u2192 ID
|
|
375
375
|
-android uiautomator \u2192 ANDROID_UIAUTOMATOR \xB7 -ios class chain \u2192 IOS_CLASS_CHAIN
|
|
376
|
-
-ios predicate string \u2192 IOS_PREDICATE (spelled as this language's binding spells them)`," Do not re-derive a strategy from `type` when `appium` is there, and never substitute one",' for another: `accessibility id "Login"` and `xpath "//*[@text=\'Login\']"` are the same'," string and DIFFERENT elements. `appium: null` means this locator has no equivalent on this"," platform \u2014 use `type`/`value` for it."," NEVER LIFT A FRAGMENT OUT OF A SELECTOR AND BUILD A SIMPLER ONE FROM IT. Given",' appium: {using: "-android uiautomator",',' value: "new UiSelector().
|
|
376
|
+
-ios predicate string \u2192 IOS_PREDICATE (spelled as this language's binding spells them)`," Do not re-derive a strategy from `type` when `appium` is there, and never substitute one",' for another: `accessibility id "Login"` and `xpath "//*[@text=\'Login\']"` are the same'," string and DIFFERENT elements. `appium: null` means this locator has no equivalent on this"," platform \u2014 use `type`/`value` for it."," NEVER LIFT A FRAGMENT OUT OF A SELECTOR AND BUILD A SIMPLER ONE FROM IT. Given",' appium: {using: "-android uiautomator",',' value: "new UiSelector().resourceId("login_username_field")"}'," the answer is",u?` getByUiSelector('new UiSelector().resourceId("login_username_field")')
|
|
377
377
|
and NOT getById('login_username_field').`:` your client's ANDROID_UIAUTOMATOR lookup with that exact string
|
|
378
|
-
and NOT its ID lookup with 'login_username_field'.`," The WHOLE string is the locator; a piece of it is a different query against a different"," set of elements
|
|
378
|
+
and NOT its ID lookup with 'login_username_field'.`," The WHOLE string is the locator; a piece of it is a different query against a different"," set of elements. The two are not even close here: UiSelector's `resourceId` compares the"," resource-id exactly as the app published it, while the `id` strategy PREFIXES the current"," app's package onto the name you give it \u2014 so it resolves nothing at all for an id that"," never had a package.",'- `"unmatched": true` means the recorder\'s own locator matched NOTHING on the page source it'," was built from. Use it if there is nothing better, and call it out in your summary: it is the"," one flag that says the recorded locator itself is suspect.",'- LOCATOR CONFIDENCE: a locator with `"ambiguous": true` matched MORE THAN ONE element on screen'," (`matchCount` says how many) \u2014 the recorder handed back its best-priority candidate anyway. Use it,"," but prefer a listed `alternatives` entry whose `matchCount` is 1 if one exists, and call out every",' ambiguous step in the summary so a human can confirm it. `matchCount: null` means "not countable",'," NOT zero \u2014 the tree-relative strategies are not attribute lookups.","- Use `context`, `anchorTag` and `anchorAttr` when present: they carry the anchor a siblingLabel /"," parentLabel / relativeIndex / precedingText locator is relative to. Dropping them turns a precise"," anchored lookup into a bare class-plus-text guess.","- A LOCATOR BUILT FROM WHAT WAS TYPED IS NOT EVIDENCE. On a `TYPE` step the element is a box"," somebody types into, and on Android its `text` is the CONTENT, not its name. So a recorded"," locator matching on the typed value \u2014 `//*[@text='invalid_user']`, `//*[@text='ada@x.com']` \u2014"," identifies that box only while that exact value is in it, which is true for the run that"," recorded it and false for every run after. Treat such a locator as NO evidence at all:"," - NEVER let it replace an existing locator. A `hint`/placeholder, id or accessibility id",' already on a page object beats it in every case, including the "same strategy, different',` value" branch of RECONCILE below \u2014 do not update a field's locator to the content of the`," field. Keep what is there and say in your summary that the recorded locator was"," content-shaped and could not verify it."," - A run of mask glyphs (`\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022`) is never a locator, anywhere, for anything. It is a"," password being drawn, it identifies nothing, and its length is the length of a secret."," - Where there is NO existing locator and the recorded one is content-shaped, prefer the"," field's placeholder/hint, a nearby label, or an `alternatives` entry that is not the"," typed value. If nothing else exists, write it, and call it out in the summary as the"," one locator in the change that will need re-recording.","- RECONCILE LOCATORS: for a recorded step whose element ALREADY has a locator in an existing page"," object, do NOT simply compare it to `locator.type`/`locator.value`. LIME reports only its own"," top pick there; `locator.alternatives[]` carries the OTHER ways it could still identify the same"," element on that screen, each with its own `matchCount`. Treat the winner plus every alternative"," as the EVIDENCE SET for what is still true of that element, and decide as follows:"," - The existing locator's strategy and value BOTH appear in the evidence set (with"," `matchCount` 1) \u2192 it still resolves. LEAVE IT UNCHANGED. This is the common case when the"," project uses an id and LIME happened to prefer an accessibility id, or vice versa; replacing"," it would swap a working locator for a different one for no reason.",' - The SAME strategy appears with a DIFFERENT value (e.g. the project has id "btn_login" and',' the evidence has id "btn_signin") \u2192 the app really did change. UPDATE just that value, keep'," the method/structure, and note it in the summary."," - The existing strategy does NOT appear in the evidence set at all \u2192 that identifier is gone."," Replace it with the recorded locator and say so in the summary."," ALTERNATIVES ARE EVIDENCE FOR THE DECISION, NEVER THE VALUE YOU WRITE. They tell you"," whether what is on disk still resolves. When a branch above says replace, replace with"," the WINNER \u2014 `locator.appium`, the one LIME picked \u2014 and never with an alternative"," because it looks simpler, shorter or more reliable. `check-locators` licenses only the"," winner, so a locator taken from `alternatives` is rejected and no pull request opens."," If you believe an alternative is genuinely better, write the winner and say so in your"," summary \u2014 that is a change for a person to make, with the app in front of them."," - `alternatives` is EMPTY and `matchCount` is null (the element had no usable attributes, so"," LIME fell back to a positional/relative xpath) \u2192 you have NO evidence about the existing"," locator. Do NOT edit it. Report in the summary that it could not be verified."," - `alternatives` is EMPTY and `matchCount` is a NUMBER \u2192 the opposite reading: LIME looked"," for every other way to identify this element and found none, so the winner is the ONLY"," identifier it has. Anything else on disk for that element \u2014 a placeholder/`hint`, a"," label, an xpath \u2014 is gone. Replace it with the winner. Do NOT preserve the existing"," locator on the grounds that the attribute it uses might be hidden in this state: for a"," `TYPE` step the field is captured BEFORE anything is typed into it, so an attribute the"," element really has is in the evidence set. (A field that WAS full when recorded gives"," itself away differently \u2014 its locator matches on the typed value, and the rule above"," refuses that outright.)"," Compare strategies like for like, normalising spellings: a recorded `resourceId` of",' "com.app:id/login_btn" is the same thing as `@AndroidFindBy(id = "login_btn")` (the package',' prefix is optional), recorded `accessibilityId` is `accessibility = "..."`, and recorded',' `uiSelector` is `uiAutomator = "..."`.',l?` If that element's page object splits per platform, you have only re-verified ${d}. Say in
|
|
379
379
|
the summary that the ${l} locator for the SAME element was not updated and may be stale for
|
|
380
380
|
the same reason, and that recording this flow on ${l} is what would refresh it. Report it
|
|
381
381
|
only \u2014 do not edit the ${l} locator (see below).`:null,"- MULTIPLE STRATEGIES ON ONE ELEMENT: a field may carry several locators at once (repeated"," `@AndroidFindBy`/`@iOSXCUITFindBy`, `@HowToUseLocators(... = LocatorGroupStrategy.ALL_POSSIBLE"," or CHAIN)`, or an equivalent list in another framework). That redundancy is deliberate."," - NEVER collapse the set into the single recorded locator. Keep every entry you do not have"," evidence against."," - Apply the rules above PER ENTRY: update only the one whose strategy matches and whose value"," the evidence contradicts, and leave its siblings exactly as they are."," - NEVER downgrade a strategy. Do not replace an id or accessibility id with an xpath. If the"," evidence only supports a weaker strategy than the one already there, add it alongside rather"," than replacing, or leave the field alone and report it.",l?`- PLATFORM SUBCLASSES: this flow was recorded on ${d}, so ONLY ${d} locators are real.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taqwright/lime-cli",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.4",
|
|
4
4
|
"description": "Thin CLI client for the LIME server — fire a natural-language QA Agent goal against a connected device and write back a taqwright spec.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Taqwright",
|