@appetize/playwright
Advanced tools
Comparing version 1.0.2-beta.1 to 1.0.2-beta.2
@@ -13,8 +13,9 @@ import { AppetizeApp } from '../api/types'; | ||
protected ready: boolean; | ||
constructor({ socket, window, logger, }: { | ||
constructor({ socket, window, logger, config, }: { | ||
socket: TSocket; | ||
window: AppetizeWindowProtocol; | ||
logger?: Logger; | ||
config?: UserSessionConfig; | ||
}); | ||
private init; | ||
protected init(): Promise<void>; | ||
waitUntilReady(): Promise<void>; | ||
@@ -21,0 +22,0 @@ startSession(config?: Partial<UserSessionConfig>): Promise<TSession>; |
@@ -137,4 +137,4 @@ /// <reference types="node" /> | ||
export interface SessionConfig { | ||
device: string; | ||
osVersion: string; | ||
device?: string; | ||
osVersion?: string; | ||
scale?: number | 'auto'; | ||
@@ -141,0 +141,0 @@ autoplay?: boolean; |
@@ -8,2 +8,3 @@ export declare function retry<T>(fn: () => T | Promise<T>, { retries, timeout, predicate, }: { | ||
export declare function uuid(): string; | ||
export declare function queryString(params: Record<string, any>): string; | ||
type CleanedObject<T> = { | ||
@@ -10,0 +11,0 @@ [P in keyof T]: T[P] extends object ? CleanedObject<T[P]> : T[P]; |
@@ -1,5 +0,5 @@ | ||
var ie=(d,w,b)=>{if(!w.has(d))throw TypeError("Cannot "+b)};var A=(d,w,b)=>(ie(d,w,"read from private field"),b?b.call(d):w.get(d)),j=(d,w,b)=>{if(w.has(d))throw TypeError("Cannot add the same private member more than once");w instanceof WeakSet?w.add(d):w.set(d,b)},B=(d,w,b,O)=>(ie(d,w,"write to private field"),O?O.call(d,b):w.set(d,b),b);(function(d,w){typeof exports=="object"&&typeof module!="undefined"?w(exports,require("events"),require("@playwright/test"),require("fs")):typeof define=="function"&&define.amd?define(["exports","events","@playwright/test","fs"],w):(d=typeof globalThis!="undefined"?globalThis:d||self,w(d.playwright={},d.events,d.test$1,d.fs))})(this,function(d,w,b,O){var k,$;"use strict";function ne(i){return i&&typeof i=="object"&&"default"in i?i:{default:i}}var I=ne(O);async function re(i,{retries:e=3,timeout:t=1e3,predicate:s=()=>!0}){for(let n=1;n<=e;n++)try{return await i()}catch(r){if(n===e||!s(r,n))throw r;await new Promise(o=>setTimeout(o,t))}throw null}function oe(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,i=>{const e=Math.random()*16|0;return(i==="x"?e:e&3|8).toString(16)})}function S(i){return Array.isArray(i)?i.map(S).filter(e=>e!=null):typeof i=="object"&&i!==null?Object.entries(i).reduce((e,[t,s])=>{const n=S(s);return n!=null&&(e[t]=n),e},{}):i}function J(i,e){if("captureStackTrace"in Error)Error.captureStackTrace(i,e);else{const t=new Error;Object.defineProperty(i,"stack",{configurable:!0,get(){const{stack:s}=t;return Object.defineProperty(this,"stack",{value:s}),s}})}}async function l(i,e){i instanceof f&&J(i,e)}class f extends Error{constructor(e){super(e),this.name="Error",this.isOperational=!0,J(this,this.constructor)}}class ae extends f{constructor(e){super(`No element found for selector | ||
${JSON.stringify(e,null,2)}`)}}class ce extends f{constructor(e){super(`Action requires 1 unique element but the selector returned ${e.length}. Provide a \`matchIndex\` to pick an element below or add additional attributes to your selector. | ||
var ie=(d,w,b)=>{if(!w.has(d))throw TypeError("Cannot "+b)};var A=(d,w,b)=>(ie(d,w,"read from private field"),b?b.call(d):w.get(d)),j=(d,w,b)=>{if(w.has(d))throw TypeError("Cannot add the same private member more than once");w instanceof WeakSet?w.add(d):w.set(d,b)},B=(d,w,b,M)=>(ie(d,w,"write to private field"),M?M.call(d,b):w.set(d,b),b);(function(d,w){typeof exports=="object"&&typeof module!="undefined"?w(exports,require("events"),require("@playwright/test"),require("fs")):typeof define=="function"&&define.amd?define(["exports","events","@playwright/test","fs"],w):(d=typeof globalThis!="undefined"?globalThis:d||self,w(d.playwright={},d.events,d.test$1,d.fs))})(this,function(d,w,b,M){var k,$;"use strict";function ne(i){return i&&typeof i=="object"&&"default"in i?i:{default:i}}var I=ne(M);async function re(i,{retries:e=3,timeout:t=1e3,predicate:s=()=>!0}){for(let n=1;n<=e;n++)try{return await i()}catch(r){if(n===e||!s(r,n))throw r;await new Promise(o=>setTimeout(o,t))}throw null}function oe(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,i=>{const e=Math.random()*16|0;return(i==="x"?e:e&3|8).toString(16)})}function ae(i){return Object.entries(i).map(([e,t])=>`${e}=${encodeURIComponent(t)}`).join("&")}function _(i){return Array.isArray(i)?i.map(_).filter(e=>e!=null):typeof i=="object"&&i!==null?Object.entries(i).reduce((e,[t,s])=>{const n=_(s);return n!=null&&(e[t]=n),e},{}):i}function J(i,e){if("captureStackTrace"in Error)Error.captureStackTrace(i,e);else{const t=new Error;Object.defineProperty(i,"stack",{configurable:!0,get(){const{stack:s}=t;return Object.defineProperty(this,"stack",{value:s}),s}})}}async function l(i,e){i instanceof f&&J(i,e)}class f extends Error{constructor(e){super(e),this.name="Error",this.isOperational=!0,J(this,this.constructor)}}class ce extends f{constructor(e){super(`No element found for selector | ||
${JSON.stringify(e,null,2)}`)}}class he extends f{constructor(e){super(`Action requires 1 unique element but the selector returned ${e.length}. Provide a \`matchIndex\` to pick an element below or add additional attributes to your selector. | ||
${ue(e)}`)}}class he extends f{constructor(e){e.localPosition?super(`localPosition (${e.localPosition.x}, ${e.localPosition.y}) for the element evaluates to a coordinate outside of screen bounds.`):super("Element is outside of screen bounds.")}}class C extends f{}class M extends f{constructor(e){super(`App Recorder must be enabled to use ${e}. Please set "record" to true in the config.`)}}function ue(i){const t=i.slice(0,5),s=i.length>5;return`${t.map((r,o)=>`// ${o} | ||
${le(e)}`)}}class ue extends f{constructor(e){e.localPosition?super(`localPosition (${e.localPosition.x}, ${e.localPosition.y}) for the element evaluates to a coordinate outside of screen bounds.`):super("Element is outside of screen bounds.")}}class C extends f{}class O extends f{constructor(e){super(`App Recorder must be enabled to use ${e}. Please set "record" to true in the config.`)}}function le(i){const t=i.slice(0,5),s=i.length>5;return`${t.map((r,o)=>`// ${o} | ||
${JSON.stringify(r,null,2)}`).join(` | ||
@@ -9,14 +9,14 @@ | ||
...and ${i.length-5} more`:""}`}async function H(i,e=5e3){const t=Date.now();let s=!1;for(;;)try{return await i(r=>{if(r)throw s=!0,r})}catch(n){if(await new Promise(r=>setTimeout(r,100)),s||e!==null&&Date.now()-t>e)throw n}}async function D(i){return new Promise(e=>setTimeout(e,i))}async function _(i,e,t){const s=typeof t=="function"?{}:t,n=typeof t=="function"?t:t==null?void 0:t.predicate,r=typeof(s==null?void 0:s.timeout)!="undefined"?s.timeout:1e4;return new Promise((o,a)=>{const c=h=>{(!n||n(h))&&(i.off(e,c),o(h))};i.on(e,c),r!==null&&setTimeout(()=>{i.off(e,c),a(new C(`Timeout ${r}ms exceeded while waiting for event "${e}"`))},r)})}function x(i){if(typeof i=="string"){if(i.endsWith("%"))return parseInt(i,10)/100;throw new Error(`Invalid position value: ${i}. Must be a number between 0 and 1, or a string ending with %`)}return i}class le{constructor({platform:e,screen:t,app:s}){this.platform=e,this.screen=t,this.app=s}pixelToDip(e){return e/(this.screen.devicePixelRatio||1)}dipToPixel(e){return e*(this.screen.devicePixelRatio||1)}getCoordinates(e,t){const s=x(e.x),n=x(e.y);return{x:s*t.width,y:n*t.height}}mapHardwareKey(e){switch(e){case"HOME":return"home";case"VOLUME_UP":return"volumeUp";case"VOLUME_DOWN":return"volumeDown"}return e}mapAction(e){return S((()=>{e=S(e);let s,n,r;if("element"in e&&e.element&&(s=this.mapElement(e.element)),"position"in e&&e.position){const o=x(e.position.x),a=x(e.position.y);if(!E.isValidNumber(o)||!E.isValidNumber(a))throw new f(`Invalid position: (${e.position.x}, ${e.position.y}). Values must be a number or a percentage`);if(!E.isPositionWithinBounds(e.position))throw typeof e.position.x=="string"?new Error(`Invalid position: (${e.position.x}, ${e.position.y}) must be within (0%, 0%) and (100%, 100%)`):new Error(`Invalid position: (${e.position.x}, ${e.position.y}) must be within (0, 0) and (1, 1)`);this.platform==="android"?n=this.getCoordinates(e.position,{width:this.dipToPixel(this.screen.width)-1,height:this.dipToPixel(this.screen.height)-1}):n=this.getCoordinates(e.position,{width:this.screen.width-1,height:this.screen.height-1})}else if("coordinates"in e&&e.coordinates){if(!E.isValidNumber(e.coordinates.x)||!E.isValidNumber(e.coordinates.y))throw new f(`Invalid coordinates: (${e.coordinates.x}, ${e.coordinates.y}). Values must be a number`);if(!E.isCoordinatesWithinBounds(e.coordinates,{width:this.screen.width-1,height:this.screen.height-1}))throw new f(`Invalid coordinates: (${e.coordinates.x}, ${e.coordinates.y}) exceed screen bounds (${this.screen.width-1}, ${this.screen.height-1})`);this.platform==="android"?n={x:this.dipToPixel(e.coordinates.x),y:this.dipToPixel(e.coordinates.y)}:n=e.coordinates}if("localPosition"in e&&e.localPosition){const o=x(e.localPosition.x),a=x(e.localPosition.y);if(!E.isValidNumber(o)||!E.isValidNumber(a))throw new f(`Invalid localPosition: (${e.localPosition.x}, ${e.localPosition.y}). Values must be a number or a percentage`);r={x:o,y:a}}else s&&(r={x:.5,y:.5});if("duration"in e&&e.duration&&!E.isValidNumber(e.duration))throw new f(`Invalid duration: ${e.duration}. Value must be a number`);switch(e.type){case"tap":{const{position:o,...a}=e;return{...a,element:s,localPosition:r,coordinates:n}}case"swipe":{const{position:o,...a}=e;return{...a,element:s,localPosition:r,coordinates:n,moves:e.moves.map(c=>{if(this.platform==="android"){const{x:h,y:u}=this.getCoordinates(c,{width:this.dipToPixel(this.screen.width)-1,height:this.dipToPixel(this.screen.height)-1});return{...c,x:h,y:u}}else{const{x:h,y:u}=this.getCoordinates(c,{width:this.screen.width-1,height:this.screen.height-1});return{...c,x:h,y:u}}})}}case"keypress":{const o=this.mapHardwareKey(e.key),a=this.mapHardwareKey(e.character);return{...e,key:o,character:a,shiftKey:this.platform==="ios"?de(e.shiftKey):e.shiftKey}}case"findElements":return{...e,element:s}}return e})())}mapElement(e){const{attributes:t,bounds:s,...n}=e,r=()=>{if(s){const{x:a,y:c,width:h,height:u}=s;return this.platform==="android"?{x:this.dipToPixel(a),y:this.dipToPixel(c),width:this.dipToPixel(h),height:this.dipToPixel(u)}:{x:L(a),y:L(c),width:L(h),height:L(u)}}},o=()=>{if(t)return Object.keys(t).reduce((a,c)=>{if(this.platform==="ios")switch(c){case"userInteractionEnabled":case"isHidden":return{...a,[c]:t[c]?"1":"0"}}else this.platform;return{...a,[c]:t[c]}},{})};return S({...n,bounds:r(),attributes:o(),accessibilityElements:void 0})}}class pe{constructor({platform:e,screen:t,app:s}){this.platform=e,this.screen=t,this.app=s}pixelToDip(e){return e/(this.screen.devicePixelRatio||1)}dipToPixel(e){return e*(this.screen.devicePixelRatio||1)}getPosition(e,t){return{x:e.x/t.width,y:e.y/t.height}}mapHardwareKey(e){switch(e){case"home":return"HOME";case"volumeUp":return"VOLUME_UP";case"volumeDown":return"VOLUME_DOWN"}return e}mapAction(e){return S((()=>{let s,n,r,o="localPosition"in e?e.localPosition:void 0;switch("coordinates"in e&&e.coordinates&&(n={x:this.pixelToDip(e.coordinates.x),y:this.pixelToDip(e.coordinates.y)},r=this.getPosition(n,{width:this.screen.width-1,height:this.screen.height-1})),"element"in e&&e.element&&(s=this.mapElement(e.element),n&&s.bounds&&(o=this.getPosition({x:n.x-s.bounds.x,y:n.y-s.bounds.y},{width:s.bounds.width,height:s.bounds.height}))),e.type){case"tap":return{...e,element:s,position:r,localPosition:o};case"swipe":return{...e,element:s,position:r,localPosition:o,moves:e.moves.map(a=>{const{x:c,y:h}=this.getPosition({x:this.pixelToDip(a.x),y:this.pixelToDip(a.y)},{width:this.screen.width-1,height:this.screen.height-1});return{x:c,y:h,t:a.t}})};case"keypress":{const a=this.mapHardwareKey(e.key),c=this.mapHardwareKey(e.character);return{...e,key:a,character:c,shiftKey:typeof e.shiftKey=="number"?we(e.shiftKey):Boolean(e.shiftKey)}}case"findElements":return{...e,element:s}}return e})())}mapUI(e){var o,a;const t=(o=e.ui)!=null?o:e.result,s=e.springboard,n=c=>{var h;return{...this.mapElement(c),children:(h=c.children)==null?void 0:h.map(n)}},r=[];return t&&(this.platform==="ios"?r.push({type:"app",appId:(a=this.app)==null?void 0:a.bundle,children:t.map(n)}):r.push({type:"app",children:t.map(n)})),s&&r.push({type:"app",appId:"com.apple.springboard",children:s.map(n)}),r}mapElement(e){const{attributes:t,bounds:s,accessibilityElements:n,...r}=e,o=h=>this.platform==="android"?{x:this.pixelToDip(h.x),y:this.pixelToDip(h.y),width:this.pixelToDip(h.width),height:this.pixelToDip(h.height)}:{x:N(h.x),y:N(h.y),width:N(h.width),height:N(h.height)},a=h=>Object.keys(h).reduce((u,p)=>{switch(p){case"userInteractionEnabled":case"isHidden":return{...u,[p]:h[p]==="1"};default:return{...u,[p]:h[p]}}},{}),c=h=>h.map(u=>{const{accessibilityFrame:p}=u;return{...a(u),accessibilityFrame:p?o(p):void 0}});return S({...r,bounds:s?o(s):void 0,attributes:t?a(t):void 0,accessibilityElements:n?c(n):void 0})}mapAppetizerEvent(e,t){var s,n;switch(e){case"debug":return{type:"log",value:t};case"interceptResponse":return{type:"network",value:{type:"response",...t}};case"interceptRequest":return{type:"network",value:{type:"request",...t}};case"interceptError":return{type:"network",value:{type:"error",...t}};case"userError":return{type:"error",value:t};case"recordedAction":return{type:"action",value:this.mapAction(t)};case"playbackFoundAndSent":{const r=t;return{type:"playbackFoundAndSent",value:{...r,playback:{...r.playback,action:r.playback.action?this.mapAction(r.playback.action):void 0},matchedElements:(s=r.matchedElements)==null?void 0:s.map(o=>{if(o)return this.mapElement(o)})}}}case"playbackError":{const r=t;return{type:"playbackError",value:{...r,playback:{...r.playback,action:r.playback.action?this.mapAction(r.playback.action):void 0},matchedElements:(n=r.matchedElements)==null?void 0:n.map(o=>{if(o)return this.mapElement(o)})}}}case"uiDump":return{type:"uiDump",value:this.mapUI(t)};case"userInteractionReceived":return{type:"interaction",value:t};case"countdownWarning":return{type:"inactivityWarning",value:t};case"h264Data":return{type:"video",value:{...t,codec:"h264"}};case"frameData":return{type:"video",value:{...t,codec:"jpeg"}};case"audioData":return{type:"audio",value:{...t,codec:"aac"}};case"deleteEvent":return null}}}class E{static isCoordinatesWithinBounds(e,t){return!(e.x<0||e.x>t.width||e.y<0||e.y>t.height)}static isPositionWithinBounds(e){const t=x(e.x),s=x(e.y);return!(t<0||t>1||s<0||s>1)}static isValidNumber(e){return!(typeof e!="number"||isNaN(e))}}function de(i){return i?1:0}function we(i){return i===1}function N(i){return typeof i=="number"?i:i==="inf"?1/0:i==="-inf"?-1/0:parseFloat(i)}function L(i){return i===1/0?"inf":i===-1/0?"-inf":i}function fe(i){if(typeof i!="object"||Array.isArray(i))throw new Error("Element must be an object");const e=Object.keys(i),s=ye(e,["text","accessibilityIdentifier","accessibilityLabel","resource-id","content-desc","class","baseClass"]);if(s.length>0){const n=s.map(r=>`'${r}'`).join(", ");throw new Error(`Element has invalid properties: ${n}. Did you mean to put these under 'attributes'?`)}return i}function ye(i,e){return i.filter(t=>e.includes(t))}function me(i){const e=i.length;let t="";for(let s=0;s<e;s+=65535){let n=65535;s+65535>e&&(n=e-s),t+=String.fromCharCode.apply(null,i.subarray(s,s+n))}return t}function ge(i,e){const t=me(i),s=btoa(t);return`data:${e};base64,`+s}class be{constructor({duration:e}){this.moves=[],this.duration=e,this.moves=[{x:0,y:0}]}to(e,t){if(typeof e!="string"||typeof t!="string")throw new f('x and y must be strings and in percentages (e.g. "50%")');if(!e.endsWith("%")||!t.endsWith("%"))throw new f('x and y must be in percentages (e.g. "50%")');return this.moves.push({x:parseFloat(e)/100,y:parseFloat(t)/100}),this}wait(e){var s;const t=this.moves[this.moves.length-1];return t&&(t.wait=e+((s=t.wait)!=null?s:0)),this}build(){var a;const t=(a=this.duration)!=null?a:Math.max(500,16*(this.moves.length-1)),s=Math.floor(t/16),n=[],r=Math.floor(s/(this.moves.length-1));let o=0;if(r===0){const c=(this.moves.length-1)*16;throw new Error(`Duration is too short for ${this.moves.length-1} moves, please set duration to at least ${c}ms`)}for(let c=0;c<this.moves.length-1;c++){const h=this.moves[c],u=this.moves[c+1],p=c===this.moves.length-2;for(let y=0;y<=r;y++){if(!p&&y===r)continue;const g=y/r,v=h.x+g*(u.x-h.x),F=h.y+g*(u.y-h.y),m=((c*r+y)*16+o)/1e3;n.push({x:v,y:F,t:m}),y===0&&h.wait&&(n.push({x:v,y:F,t:m+h.wait/1e3}),o+=h.wait)}}return n}up(e="50%"){const t=parseFloat(e);return this.to("0%",`-${t}%`)}down(e="50%"){const t=parseFloat(e);return this.to("0%",`${t}%`)}left(e="50%"){const t=parseFloat(e);return this.to(`-${t}%`,"0%")}right(e="50%"){const t=parseFloat(e);return this.to(`${t}%`,"0%")}}class U{constructor(){this.log=function(){const e="[Appetize]";return Function.prototype.bind.call(console.log,console,e)}(),this.warn=function(){const e="[Appetize]";return Function.prototype.bind.call(console.warn,console,e)}(),this.error=function(){const e="[Appetize]";return Function.prototype.bind.call(console.error,console,e)}(),this.debug=function(){const e="[Appetize]";return Function.prototype.bind.call(console.debug,console,e)}()}}class ve extends w.EventEmitter{constructor({socket:t,config:s,path:n,token:r,app:o,device:a,logger:c=new U}){super();j(this,k,void 0);j(this,$,void 0);this.isEndingManually=!1,this.countdownWarning=!1,this.ready=!1,this.actionLogs=[],this.config=s,this.socket=t,this.device=a,this.app=o,this.path=n,this.token=r,this.logger=c;const h=({type:u,value:p})=>{const g=new pe({platform:this.config.platform,screen:this.device.screen,app:this.app}).mapAppetizerEvent(u,p);switch(u){case"ready":this.ready=!0;break;case"adbOverTcp":{B(this,k,{...p,command:Ee(p)});break}case"networkInspectorUrl":B(this,$,p);break;case"countdownWarning":this.countdownWarning=!0;break;case"timeoutReset":this.countdownWarning=!1;break;case"deviceInfo":this.device=p;break}g?(this.emit(g.type,g.value),this.emit("*",g)):g!==null&&(this.emit(u,p),this.emit("*",{type:u,value:p}))};this.socket.on("*",h),this.on("disconnect",()=>{this.socket.off("*",h),this.isEndingManually||(this.countdownWarning?this.logger.warn("Appetize session has ended due to inactivity"):this.logger.warn("Session disconnected"))})}on(t,s){return t==="network"&&this.config.proxy!=="intercept"&&this.logger.warn('Session must be configured with `proxy: "intercept"` to listen to network events.'),t==="log"&&this.config.debug!==!0&&this.logger.warn("Session must be configured with `debug: true` to listen to log events."),t==="action"&&this.config.record!==!0&&this.logger.warn("Session must configured with `record: true` to listen to action events."),super.on(t,s)}async waitUntilReady(){let t=!0;const s=async r=>new Promise(o=>{const a=setInterval(()=>{r()&&o(void 0)},10);setTimeout(()=>{clearInterval(a),o(void 0)},3e3)}),n=()=>{t=!1};this.socket.once("disconnect",n);try{await H(r=>{if(!this.ready){if(t)throw new C("Timed out after 180s waiting for session to be ready");r(new Error("Session disconnected"))}},18e4)}finally{this.socket.off("disconnect",n)}await Promise.all([this.config.proxy==="intercept"?s(()=>Boolean(A(this,$))):Promise.resolve(),this.config.enableAdb?s(()=>Boolean(A(this,k))):Promise.resolve()])}async waitForEvent(t,s){try{return await _(this,t,s)}catch(n){throw l(n,this.waitForEvent),n}}async end(){this.isEndingManually=!0,await this.socket.disconnect()}get networkInspectorUrl(){return this.config.proxy!=="intercept"&&this.logger.warn('Session must be configured with `proxy: "intercept"` to use the network inspector'),A(this,$)}get adbConnection(){if(this.config.platform&&this.config.platform!=="android"&&this.logger.warn("Session must be connected to an Android device to use adb"),this.config.enableAdb||this.logger.warn("Session must be configured with `enableAdb: true` to use adb"),A(this,k))return A(this,k)}async rotate(t){try{const[s]=await Promise.all([this.waitForEvent("orientationChanged"),this.socket.send("userInteraction",{type:"keypress",key:t==="left"?"rotateLeft":"rotateRight",timeStamp:Date.now()})]);return s}catch(s){throw l(s,this.rotate),s}}async screenshot(t="buffer"){try{this.socket.send("getScreenshot");const s=await _(this.socket,"screenshot",{timeout:6e4});if(!s.success)throw new f("Screenshot failed");return{data:t==="buffer"?(o=>typeof window=="undefined"?Buffer.from(o):o)(s.data):ge(new Uint8Array(s.data),s.mimeType),mimeType:s.mimeType}}catch(s){throw l(s,this.screenshot),s}}async heartbeat(){try{return await this.socket.send("heartbeat")}catch(t){throw l(t,this.heartbeat),t}}async type(t){try{return this.config.platform==="ios"&&await D(1e3),await this.playAction({type:"typeText",text:t})}catch(s){throw l(s,this.type),s}}async keypress(t,s){try{if(t==="ANDROID_KEYCODE_MENU")return await this.socket.send("androidKeycodeMenu");if((s==null?void 0:s.shift)||t==="HOME"){switch(t){case"ArrowUp":t="arrowUp";break;case"ArrowDown":t="arrowDown";break;case"ArrowLeft":t="arrowLeft";break;case"ArrowRight":t="arrowRight";break;case"Enter":t="\r";break;case"Tab":t=" ";break;case"Backspace":t="\b";break}return this.playAction({type:"keypress",key:t,shiftKey:!!(s!=null&&s.shift)})}else return this.playAction({type:"keypress",character:t})}catch(n){throw l(n,this.keypress),n}}async setLanguage(t){try{return this.config.language=t,await this.socket.send("setLanguage",{language:t,timeStamp:Date.now()})}catch(s){throw l(s,this.setLanguage),s}}async setLocation(t,s){try{if(typeof t!="number"||typeof s!="number")throw new f("setLocation requires latitude and longitude to be numbers");const n=[t,s];return this.config.location=n,await this.socket.send("setLocation",{location:n,timeStamp:Date.now()})}catch(n){throw l(n,this.setLocation),n}}async openUrl(t){try{return await this.socket.send("openUrl",{url:t,timeStamp:Date.now()})}catch(s){throw l(s,this.openUrl),s}}async shake(){try{return await this.socket.send("shakeDevice")}catch(t){throw l(t,this.swipe),t}}async biometry({match:t}){try{return await this.socket.send(t?"biometryMatch":"biometryNoMatch")}catch(s){throw l(s,this.biometry),s}}async allowInteractions(t){try{return await this.socket.send(t?"enableInteractions":"disableInteractions")}catch(s){throw l(s,this.allowInteractions),s}}async restartApp(){try{this.socket.send("restartApp");const{platform:t}=this.config;t==="ios"?await this.waitForEvent("appLaunch",{timeout:6e4}):await D(1e3)}catch(t){throw l(t,this.restartApp),t}}async reinstallApp(){try{this.socket.send("reinstallApp"),await this.waitForEvent("appLaunch",{timeout:6e4})}catch(t){throw l(t,this.reinstallApp),t}}async adbShellCommand(t){if(this.config.platform!=="android")throw new Error("adbShellCommand is only available on Android devices");try{return await this.socket.send("adbShellCommand",{command:t,timeStamp:Date.now()})}catch(s){throw l(s,this.adbShellCommand),s}}async playAction(t,s={}){const{timeout:n=1e4}=s;try{if(!this.config.record)throw new M("playAction()");if(isNaN(n))throw new f(`Invalid timeout value: ${s.timeout}`);if(n<0)throw new f(`Timeout value cannot be negative: ${s.timeout}`);"element"in t&&t.element&&fe(t.element);const r=new le({platform:this.config.platform,screen:this.device.screen,app:this.app}),o=oe(),c=s.noMap?t:r.mapAction(t),h={id:o,action:c,options:{...s,timeout:Math.round(n/1e3)}},[u]=await Promise.all([new Promise((p,y)=>{const g=()=>{this.off("playbackFoundAndSent",v),this.off("playbackError",F)},v=async m=>{var R;((R=m.playback)==null?void 0:R.id)===o&&(await this.addActionLog({action:t,payload:h,result:m}),g(),p(m))},F=async m=>{var R;if(((R=m.playback)==null?void 0:R.id)===o)switch(await this.addActionLog({action:t,payload:h,error:m}),g(),m.errorId){case"internalError":y(new f(`An internal error has occurred for the action: | ||
${JSON.stringify(t,null,2)}`));break;case"notFound":if(m.playback!==void 0&&"element"in m.playback.action&&m.playback.action.element!==void 0){y(new ae(m.playback.action.element));break}case"ambiguousMatch":if("matchedElements"in m&&m.matchedElements!==void 0){y(new ce(m.matchedElements));break}case"invalidArgument":if("element"in t&&t.element&&m.message.match("outside the screen bounds")){y(new he(t));break}default:y(new f(m.message));break}};this.once("playbackFoundAndSent",v),this.once("playbackError",F)}),this.socket.send("playAction",h)]);return u}catch(r){throw l(r,this.playAction),r}}async playActions(t,s={}){try{if(!this.config.record)throw new M("playActions()");const n=[];for(const r of t){const o=await this.playAction(r,s);n.push(o);const a=t[t.indexOf(r)+1];a&&a.type==="keypress"&&r.type==="keypress"||await D(1e3)}return n}catch(n){throw l(n,this.playActions),n}}async getUI({timeout:t=3e4}={}){try{return this.socket.send("dumpUi"),await _(this,"uiDump",{timeout:t})}catch(s){throw l(s,this.getUI),s}}async findElement(t,s){var n;try{return(n=(await this.playAction({type:"findElements",element:t,appId:s==null?void 0:s.appId},s)).matchedElements)==null?void 0:n[0]}catch(r){throw l(r,this.findElement),r}}async findElements(t,s){try{return(await this.playAction({type:"findElements",element:t,appId:s==null?void 0:s.appId},s)).matchedElements}catch(n){throw l(n,this.findElements),n}}async tap(t,s){var n;try{if(!this.config.record)throw new M("tap()");return await this.playAction({type:"tap",...t,duration:((n=t.duration)!=null?n:0)/1e3},s)}catch(r){throw l(r,this.tap),r}}async swipe({duration:t,gesture:s,...n},r){try{if(!this.config.record)throw new M("swipe()");let o;const a=new be({duration:t});if(typeof s=="function")s(a);else switch(s){case"up":a.up();break;case"down":a.down();break;case"left":a.left();break;case"right":a.right();break}if("element"in n)o={type:"swipe",element:n.element,localPosition:n.localPosition,moves:a.build()};else if("position"in n)o={type:"swipe",position:n.position,moves:a.build()};else throw new Error("Either element or position must be specified");return this.playAction(o,r)}catch(o){throw l(o,this.swipe),o}}async addActionLog(t){this.actionLogs.push(t)}async getAdbInfo(){return this.logger.warn("getAdbInfo() is deprecated. Please use the `adbConnection` property instead."),Promise.resolve(A(this,k))}async getNetworkInspectorUrl(){return this.logger.warn("getNetworkInspectorUrl() is deprecated. Please use the `networkInspectorUrl` property instead."),Promise.resolve(A(this,$))}async getDeviceInfo(){return this.logger.warn("getDeviceInfo() is deprecated. Please use the `device` property instead."),Promise.resolve(this.device)}}k=new WeakMap,$=new WeakMap;function Ee(i){const e="ssh -fN -o StrictHostKeyChecking=no -oHostKeyAlgorithms=+ssh-rsa -p SERVER_PORT USERNAME@HOSTNAME -L6000:FORWARD_DESTINATION:FORWARD_PORT && adb connect localhost:6000";if(!i||!i.forwards[0])return;let t=e;return t=t.replace(/SERVER_PORT/,i.port.toString()),t=t.replace(/USERNAME/,i.user),t=t.replace(/HOSTNAME/,i.hostname),t=t.replace(/FORWARD_DESTINATION/,i.forwards[0].destination),t=t.replace(/FORWARD_PORT/,i.forwards[0].port.toString()),t}b.expect.extend({toHaveElement:async(i,e,t={})=>{try{const s=await i.findElements(e,t);return{pass:typeof t.matches=="number"?s.length===t.matches:s.length>0,message:()=>`Element not found: | ||
${JSON.stringify(e,null,2)}`}}catch(s){return{pass:!1,message:()=>s.message}}}});class xe extends w.EventEmitter{constructor({socket:e,logger:t=new U}){super(),this.logger=t,this.socket=e,this.socket.on("*",({type:s,value:n})=>{const r=ke(s,n);r!==null&&(r?(this.emit(r.type,r.value),this.emit("*",r)):(this.emit(s,n),this.emit("*",{type:s,value:n})))})}on(e,t){return super.on(e,t)}async startSession(e){throw new Error("Not implemented")}async setConfig(e){throw new Error("Not implemented")}getConfig(){return this._config}async waitForSessionStart(e){return new Promise(async(t,s)=>{const n=()=>{s(new Error("Session disconnected before it was ready"))},r=a=>{s(new Error(`Session failed to start - ${typeof a.message=="object"?JSON.stringify(a.message):a.message}`))},o=a=>{a.message.match(/Too many requests/)&&s(new Error("Session failed to start due to too many requests"))};try{this.on("error",o),e.on("disconnect",n),e.on("error",r),await e.waitUntilReady()}finally{this.off("error",o),e.off("disconnect",n),e.off("error",r)}t(e)})}}function ke(i,e){switch(i){case"concurrentQueue":return{type:"queue",value:{type:"concurrent",name:e.name,position:e.position}};case"queue":return{type:"queue",value:{type:"session",position:e.position}};case"userError":return{type:"error",value:e};case"newSession":return null}}class Ae extends xe{constructor({socket:e,window:t,logger:s=new U}){super({socket:e,logger:s}),this.ready=!1,this.window=t,this.window.on("*",async({type:n,value:r})=>{switch(n){case"app":this.app=r,this.emit(n,r);break;case"deviceInfo":this.device=r,this.emit(n,r);break;case"config":this._config=this.mapConfig(r);break}}),this.window.on("iframeload",()=>{this.ready=!1,this.session=void 0,this.init()}),this.socket.on("*",async({type:n,value:r})=>{switch(n){case"newSession":try{this.session=this.createSession(this._config,{path:r.path,token:r.sessionToken}),await this.waitForSessionStart(this.session),this.emit("session",this.session)}catch(o){this.emit("sessionError",o)}}}),this.init()}async init(){await this.window.waitUntilReady();const[e,t,s]=await Promise.all([this.window.postMessage({type:"getApp"},!0),this.window.postMessage({type:"getDeviceInfo"},!0),this.setConfig({record:!0})]);this.app=e,this.device=t,this._config=s,this.ready=!0}async waitUntilReady(){if(!this.ready)return H(async()=>{if(!this.ready)throw new Error("Timed out waiting for client to be ready")},3e4)}async startSession(e){try{await this.waitUntilReady()}catch(s){const n=s instanceof Error?s.message:s;throw new Error(`Failed to start session. ${n}`)}this.session&&await this.session.end(),await this.setConfig(e!=null?e:{});const[t]=await Promise.all([new Promise((s,n)=>{const r=a=>{this.off("session",r),this.off("sessionError",o),s(a)},o=a=>{this.off("session",r),this.off("sessionError",o),n(a)};this.on("session",r),this.on("sessionError",o)}),this.window.postMessage({type:"requestSession"},!0)]);return t}async config(e){return this.logger.warn("client.config() is deprecated and will be removed in a future major release. Use client.setConfig() instead."),this.setConfig(e)}async setConfig({publicKey:e,...t}){if(e){const n=await this.window.postMessage({type:"loadApp",value:e},!0);if(n&&"error"in n)throw new Error(n.error)}const s=await this.window.postMessage({type:"setConfig",value:this.validateConfig(t!=null?t:{})},!0).then(this.mapConfig);return this._config=s,s}mapConfig(e){return{...e,device:e.deviceType||e.device}}validateConfig(e){return e}createSession(e,t){throw new Error("Not implemented")}}class Se extends U{constructor(){super(...arguments),this.logHistory=[],this.logLevel=process.env.CI==="true"?"warnings-errors":"verbose",this.log=(...e)=>{if(this.logHistory.push({method:"log",data:e}),this.logLevel==="verbose")return console.log(...e)},this.warn=(...e)=>{if(this.logHistory.push({method:"warn",data:e}),this.logLevel==="verbose"||this.logLevel==="warnings-errors")return console.warn(...e)},this.error=(...e)=>{if(this.logHistory.push({method:"error",data:e}),this.logLevel==="verbose"||this.logLevel==="warnings-errors")return console.error(...e)},this.debug=(...e)=>{if(this.logHistory.push({method:"debug",data:e}),this.logLevel==="verbose")return console.debug(...e)},this.clearLogHistory=()=>{this.logHistory=[]}}}const T=new Se;async function _e(i){await i.pause()}function V(i){if(i.character)return i.character;const e=i.key;return z(e)?e:i.shiftKey?e.toUpperCase():e.toLowerCase()}function z(i){return/^[\b\t\r]/.test(i)}class Te{constructor({testInfo:e,session:t}){this.currentRecord=0,this.session=t,this.testInfo=e}async record(){const e=this.testInfo.file,t=await I.default.promises.readFile(e,"utf8"),n=t.split(` | ||
`).map((r,o)=>({line:r,num:o+1})).slice(this.testInfo.line).filter(({line:r})=>r.includes("session.record()"))[this.currentRecord].num;if(n!==void 0){console.log(`\u{1F534} Recording at line ${n}`);const r=[],o=c=>{Pe(r,c),console.log(Y(c))};this.session.on("action",o),await _e(this.session.page),await D(2e3),this.session.off("action",o);const a=t.split(` | ||
`).map((c,h)=>{var u,p;if(h===n-1){const y=(p=(u=c.match(/^\s*/))==null?void 0:u[0])!=null?p:0;return`${r.map(v=>Y(v)).reduce((v,F,m)=>`${v} | ||
// ${m+1}. ${F}`,"// Recorded using session.record()")} | ||
...and ${i.length-5} more`:""}`}async function H(i,e=5e3){const t=Date.now();let s=!1;for(;;)try{return await i(r=>{if(r)throw s=!0,r})}catch(n){if(await new Promise(r=>setTimeout(r,100)),s||e!==null&&Date.now()-t>e)throw n}}async function D(i){return new Promise(e=>setTimeout(e,i))}async function S(i,e,t){const s=typeof t=="function"?{}:t,n=typeof t=="function"?t:t==null?void 0:t.predicate,r=typeof(s==null?void 0:s.timeout)!="undefined"?s.timeout:1e4;return new Promise((o,a)=>{const c=h=>{(!n||n(h))&&(i.off(e,c),o(h))};i.on(e,c),r!==null&&setTimeout(()=>{i.off(e,c),a(new C(`Timeout ${r}ms exceeded while waiting for event "${e}"`))},r)})}function x(i){if(typeof i=="string"){if(i.endsWith("%"))return parseInt(i,10)/100;throw new Error(`Invalid position value: ${i}. Must be a number between 0 and 1, or a string ending with %`)}return i}class pe{constructor({platform:e,screen:t,app:s}){this.platform=e,this.screen=t,this.app=s}pixelToDip(e){return e/(this.screen.devicePixelRatio||1)}dipToPixel(e){return e*(this.screen.devicePixelRatio||1)}getCoordinates(e,t){const s=x(e.x),n=x(e.y);return{x:s*t.width,y:n*t.height}}mapHardwareKey(e){switch(e){case"HOME":return"home";case"VOLUME_UP":return"volumeUp";case"VOLUME_DOWN":return"volumeDown"}return e}mapAction(e){return _((()=>{e=_(e);let s,n,r;if("element"in e&&e.element&&(s=this.mapElement(e.element)),"position"in e&&e.position){const o=x(e.position.x),a=x(e.position.y);if(!E.isValidNumber(o)||!E.isValidNumber(a))throw new f(`Invalid position: (${e.position.x}, ${e.position.y}). Values must be a number or a percentage`);if(!E.isPositionWithinBounds(e.position))throw typeof e.position.x=="string"?new Error(`Invalid position: (${e.position.x}, ${e.position.y}) must be within (0%, 0%) and (100%, 100%)`):new Error(`Invalid position: (${e.position.x}, ${e.position.y}) must be within (0, 0) and (1, 1)`);this.platform==="android"?n=this.getCoordinates(e.position,{width:this.dipToPixel(this.screen.width)-1,height:this.dipToPixel(this.screen.height)-1}):n=this.getCoordinates(e.position,{width:this.screen.width-1,height:this.screen.height-1})}else if("coordinates"in e&&e.coordinates){if(!E.isValidNumber(e.coordinates.x)||!E.isValidNumber(e.coordinates.y))throw new f(`Invalid coordinates: (${e.coordinates.x}, ${e.coordinates.y}). Values must be a number`);if(!E.isCoordinatesWithinBounds(e.coordinates,{width:this.screen.width-1,height:this.screen.height-1}))throw new f(`Invalid coordinates: (${e.coordinates.x}, ${e.coordinates.y}) exceed screen bounds (${this.screen.width-1}, ${this.screen.height-1})`);this.platform==="android"?n={x:this.dipToPixel(e.coordinates.x),y:this.dipToPixel(e.coordinates.y)}:n=e.coordinates}if("localPosition"in e&&e.localPosition){const o=x(e.localPosition.x),a=x(e.localPosition.y);if(!E.isValidNumber(o)||!E.isValidNumber(a))throw new f(`Invalid localPosition: (${e.localPosition.x}, ${e.localPosition.y}). Values must be a number or a percentage`);r={x:o,y:a}}else s&&(r={x:.5,y:.5});if("duration"in e&&e.duration&&!E.isValidNumber(e.duration))throw new f(`Invalid duration: ${e.duration}. Value must be a number`);switch(e.type){case"tap":{const{position:o,...a}=e;return{...a,element:s,localPosition:r,coordinates:n}}case"swipe":{const{position:o,...a}=e;return{...a,element:s,localPosition:r,coordinates:n,moves:e.moves.map(c=>{if(this.platform==="android"){const{x:h,y:u}=this.getCoordinates(c,{width:this.dipToPixel(this.screen.width)-1,height:this.dipToPixel(this.screen.height)-1});return{...c,x:h,y:u}}else{const{x:h,y:u}=this.getCoordinates(c,{width:this.screen.width-1,height:this.screen.height-1});return{...c,x:h,y:u}}})}}case"keypress":{const o=this.mapHardwareKey(e.key),a=this.mapHardwareKey(e.character);return{...e,key:o,character:a,shiftKey:this.platform==="ios"?we(e.shiftKey):e.shiftKey}}case"findElements":return{...e,element:s}}return e})())}mapElement(e){const{attributes:t,bounds:s,...n}=e,r=()=>{if(s){const{x:a,y:c,width:h,height:u}=s;return this.platform==="android"?{x:this.dipToPixel(a),y:this.dipToPixel(c),width:this.dipToPixel(h),height:this.dipToPixel(u)}:{x:L(a),y:L(c),width:L(h),height:L(u)}}},o=()=>{if(t)return Object.keys(t).reduce((a,c)=>{if(this.platform==="ios")switch(c){case"userInteractionEnabled":case"isHidden":return{...a,[c]:t[c]?"1":"0"}}else this.platform;return{...a,[c]:t[c]}},{})};return _({...n,bounds:r(),attributes:o(),accessibilityElements:void 0})}}class de{constructor({platform:e,screen:t,app:s}){this.platform=e,this.screen=t,this.app=s}pixelToDip(e){return e/(this.screen.devicePixelRatio||1)}dipToPixel(e){return e*(this.screen.devicePixelRatio||1)}getPosition(e,t){return{x:e.x/t.width,y:e.y/t.height}}mapHardwareKey(e){switch(e){case"home":return"HOME";case"volumeUp":return"VOLUME_UP";case"volumeDown":return"VOLUME_DOWN"}return e}mapAction(e){return _((()=>{let s,n,r,o="localPosition"in e?e.localPosition:void 0;switch("coordinates"in e&&e.coordinates&&(n={x:this.pixelToDip(e.coordinates.x),y:this.pixelToDip(e.coordinates.y)},r=this.getPosition(n,{width:this.screen.width-1,height:this.screen.height-1})),"element"in e&&e.element&&(s=this.mapElement(e.element),n&&s.bounds&&(o=this.getPosition({x:n.x-s.bounds.x,y:n.y-s.bounds.y},{width:s.bounds.width,height:s.bounds.height}))),e.type){case"tap":return{...e,element:s,position:r,localPosition:o};case"swipe":return{...e,element:s,position:r,localPosition:o,moves:e.moves.map(a=>{const{x:c,y:h}=this.getPosition({x:this.pixelToDip(a.x),y:this.pixelToDip(a.y)},{width:this.screen.width-1,height:this.screen.height-1});return{x:c,y:h,t:a.t}})};case"keypress":{const a=this.mapHardwareKey(e.key),c=this.mapHardwareKey(e.character);return{...e,key:a,character:c,shiftKey:typeof e.shiftKey=="number"?fe(e.shiftKey):Boolean(e.shiftKey)}}case"findElements":return{...e,element:s}}return e})())}mapUI(e){var o,a;const t=(o=e.ui)!=null?o:e.result,s=e.springboard,n=c=>{var h;return{...this.mapElement(c),children:(h=c.children)==null?void 0:h.map(n)}},r=[];return t&&(this.platform==="ios"?r.push({type:"app",appId:(a=this.app)==null?void 0:a.bundle,children:t.map(n)}):r.push({type:"app",children:t.map(n)})),s&&r.push({type:"app",appId:"com.apple.springboard",children:s.map(n)}),r}mapElement(e){const{attributes:t,bounds:s,accessibilityElements:n,...r}=e,o=h=>this.platform==="android"?{x:this.pixelToDip(h.x),y:this.pixelToDip(h.y),width:this.pixelToDip(h.width),height:this.pixelToDip(h.height)}:{x:N(h.x),y:N(h.y),width:N(h.width),height:N(h.height)},a=h=>Object.keys(h).reduce((u,p)=>{switch(p){case"userInteractionEnabled":case"isHidden":return{...u,[p]:h[p]==="1"};default:return{...u,[p]:h[p]}}},{}),c=h=>h.map(u=>{const{accessibilityFrame:p}=u;return{...a(u),accessibilityFrame:p?o(p):void 0}});return _({...r,bounds:s?o(s):void 0,attributes:t?a(t):void 0,accessibilityElements:n?c(n):void 0})}mapAppetizerEvent(e,t){var s,n;switch(e){case"debug":return{type:"log",value:t};case"interceptResponse":return{type:"network",value:{type:"response",...t}};case"interceptRequest":return{type:"network",value:{type:"request",...t}};case"interceptError":return{type:"network",value:{type:"error",...t}};case"userError":return{type:"error",value:t};case"recordedAction":return{type:"action",value:this.mapAction(t)};case"playbackFoundAndSent":{const r=t;return{type:"playbackFoundAndSent",value:{...r,playback:{...r.playback,action:r.playback.action?this.mapAction(r.playback.action):void 0},matchedElements:(s=r.matchedElements)==null?void 0:s.map(o=>{if(o)return this.mapElement(o)})}}}case"playbackError":{const r=t;return{type:"playbackError",value:{...r,playback:{...r.playback,action:r.playback.action?this.mapAction(r.playback.action):void 0},matchedElements:(n=r.matchedElements)==null?void 0:n.map(o=>{if(o)return this.mapElement(o)})}}}case"uiDump":return{type:"uiDump",value:this.mapUI(t)};case"userInteractionReceived":return{type:"interaction",value:t};case"countdownWarning":return{type:"inactivityWarning",value:t};case"h264Data":return{type:"video",value:{...t,codec:"h264"}};case"frameData":return{type:"video",value:{...t,codec:"jpeg"}};case"audioData":return{type:"audio",value:{...t,codec:"aac"}};case"deleteEvent":return null}}}class E{static isCoordinatesWithinBounds(e,t){return!(e.x<0||e.x>t.width||e.y<0||e.y>t.height)}static isPositionWithinBounds(e){const t=x(e.x),s=x(e.y);return!(t<0||t>1||s<0||s>1)}static isValidNumber(e){return!(typeof e!="number"||isNaN(e))}}function we(i){return i?1:0}function fe(i){return i===1}function N(i){return typeof i=="number"?i:i==="inf"?1/0:i==="-inf"?-1/0:parseFloat(i)}function L(i){return i===1/0?"inf":i===-1/0?"-inf":i}function me(i){if(typeof i!="object"||Array.isArray(i))throw new Error("Element must be an object");const e=Object.keys(i),s=ye(e,["text","accessibilityIdentifier","accessibilityLabel","resource-id","content-desc","class","baseClass"]);if(s.length>0){const n=s.map(r=>`'${r}'`).join(", ");throw new Error(`Element has invalid properties: ${n}. Did you mean to put these under 'attributes'?`)}return i}function ye(i,e){return i.filter(t=>e.includes(t))}function ge(i){const e=i.length;let t="";for(let s=0;s<e;s+=65535){let n=65535;s+65535>e&&(n=e-s),t+=String.fromCharCode.apply(null,i.subarray(s,s+n))}return t}function be(i,e){const t=ge(i),s=btoa(t);return`data:${e};base64,`+s}class ve{constructor({duration:e}){this.moves=[],this.duration=e,this.moves=[{x:0,y:0}]}to(e,t){if(typeof e!="string"||typeof t!="string")throw new f('x and y must be strings and in percentages (e.g. "50%")');if(!e.endsWith("%")||!t.endsWith("%"))throw new f('x and y must be in percentages (e.g. "50%")');return this.moves.push({x:parseFloat(e)/100,y:parseFloat(t)/100}),this}wait(e){var s;const t=this.moves[this.moves.length-1];return t&&(t.wait=e+((s=t.wait)!=null?s:0)),this}build(){var a;const t=(a=this.duration)!=null?a:Math.max(500,16*(this.moves.length-1)),s=Math.floor(t/16),n=[],r=Math.floor(s/(this.moves.length-1));let o=0;if(r===0){const c=(this.moves.length-1)*16;throw new Error(`Duration is too short for ${this.moves.length-1} moves, please set duration to at least ${c}ms`)}for(let c=0;c<this.moves.length-1;c++){const h=this.moves[c],u=this.moves[c+1],p=c===this.moves.length-2;for(let m=0;m<=r;m++){if(!p&&m===r)continue;const g=m/r,v=h.x+g*(u.x-h.x),F=h.y+g*(u.y-h.y),y=((c*r+m)*16+o)/1e3;n.push({x:v,y:F,t:y}),m===0&&h.wait&&(n.push({x:v,y:F,t:y+h.wait/1e3}),o+=h.wait)}}return n}up(e="50%"){const t=parseFloat(e);return this.to("0%",`-${t}%`)}down(e="50%"){const t=parseFloat(e);return this.to("0%",`${t}%`)}left(e="50%"){const t=parseFloat(e);return this.to(`-${t}%`,"0%")}right(e="50%"){const t=parseFloat(e);return this.to(`${t}%`,"0%")}}class U{constructor(){this.log=function(){const e="[Appetize]";return Function.prototype.bind.call(console.log,console,e)}(),this.warn=function(){const e="[Appetize]";return Function.prototype.bind.call(console.warn,console,e)}(),this.error=function(){const e="[Appetize]";return Function.prototype.bind.call(console.error,console,e)}(),this.debug=function(){const e="[Appetize]";return Function.prototype.bind.call(console.debug,console,e)}()}}class Ee extends w.EventEmitter{constructor({socket:t,config:s,path:n,token:r,app:o,device:a,logger:c=new U}){super();j(this,k,void 0);j(this,$,void 0);this.isEndingManually=!1,this.countdownWarning=!1,this.ready=!1,this.actionLogs=[],this.config=s,this.socket=t,this.device=a,this.app=o,this.path=n,this.token=r,this.logger=c;const h=({type:u,value:p})=>{const g=new de({platform:this.config.platform,screen:this.device.screen,app:this.app}).mapAppetizerEvent(u,p);switch(u){case"ready":this.ready=!0;break;case"adbOverTcp":{B(this,k,{...p,command:xe(p)});break}case"networkInspectorUrl":B(this,$,p);break;case"countdownWarning":this.countdownWarning=!0;break;case"timeoutReset":this.countdownWarning=!1;break;case"deviceInfo":this.device=p;break}g?(this.emit(g.type,g.value),this.emit("*",g)):g!==null&&(this.emit(u,p),this.emit("*",{type:u,value:p}))};this.socket.on("*",h),this.on("disconnect",()=>{this.socket.off("*",h),this.isEndingManually||(this.countdownWarning?this.logger.warn("Appetize session has ended due to inactivity"):this.logger.warn("Session disconnected"))})}on(t,s){return t==="network"&&this.config.proxy!=="intercept"&&this.logger.warn('Session must be configured with `proxy: "intercept"` to listen to network events.'),t==="log"&&this.config.debug!==!0&&this.logger.warn("Session must be configured with `debug: true` to listen to log events."),t==="action"&&this.config.record!==!0&&this.logger.warn("Session must configured with `record: true` to listen to action events."),super.on(t,s)}async waitUntilReady(){let t=!0;const s=async r=>new Promise(o=>{const a=setInterval(()=>{r()&&o(void 0)},10);setTimeout(()=>{clearInterval(a),o(void 0)},3e3)}),n=()=>{t=!1};this.socket.once("disconnect",n);try{await H(r=>{if(!this.ready){if(t)throw new C("Timed out after 180s waiting for session to be ready");r(new Error("Session disconnected"))}},18e4)}finally{this.socket.off("disconnect",n)}await Promise.all([this.config.proxy==="intercept"?s(()=>Boolean(A(this,$))):Promise.resolve(),this.config.enableAdb?s(()=>Boolean(A(this,k))):Promise.resolve()])}async waitForEvent(t,s){try{return await S(this,t,s)}catch(n){throw l(n,this.waitForEvent),n}}async end(){this.isEndingManually=!0,await this.socket.disconnect()}get networkInspectorUrl(){return this.config.proxy!=="intercept"&&this.logger.warn('Session must be configured with `proxy: "intercept"` to use the network inspector'),A(this,$)}get adbConnection(){if(this.config.platform&&this.config.platform!=="android"&&this.logger.warn("Session must be connected to an Android device to use adb"),this.config.enableAdb||this.logger.warn("Session must be configured with `enableAdb: true` to use adb"),A(this,k))return A(this,k)}async rotate(t){try{const[s]=await Promise.all([this.waitForEvent("orientationChanged"),this.socket.send("userInteraction",{type:"keypress",key:t==="left"?"rotateLeft":"rotateRight",timeStamp:Date.now()})]);return s}catch(s){throw l(s,this.rotate),s}}async screenshot(t="buffer"){try{this.socket.send("getScreenshot");const s=await S(this.socket,"screenshot",{timeout:6e4});if(!s.success)throw new f("Screenshot failed");return{data:t==="buffer"?(o=>typeof window=="undefined"?Buffer.from(o):o)(s.data):be(new Uint8Array(s.data),s.mimeType),mimeType:s.mimeType}}catch(s){throw l(s,this.screenshot),s}}async heartbeat(){try{return await this.socket.send("heartbeat")}catch(t){throw l(t,this.heartbeat),t}}async type(t){try{return this.config.platform==="ios"&&await D(1e3),await this.playAction({type:"typeText",text:t})}catch(s){throw l(s,this.type),s}}async keypress(t,s){try{if(t==="ANDROID_KEYCODE_MENU")return await this.socket.send("androidKeycodeMenu");if((s==null?void 0:s.shift)||t==="HOME"){switch(t){case"ArrowUp":t="arrowUp";break;case"ArrowDown":t="arrowDown";break;case"ArrowLeft":t="arrowLeft";break;case"ArrowRight":t="arrowRight";break;case"Enter":t="\r";break;case"Tab":t=" ";break;case"Backspace":t="\b";break}return this.playAction({type:"keypress",key:t,shiftKey:!!(s!=null&&s.shift)})}else return this.playAction({type:"keypress",character:t})}catch(n){throw l(n,this.keypress),n}}async setLanguage(t){try{return this.config.language=t,await this.socket.send("setLanguage",{language:t,timeStamp:Date.now()})}catch(s){throw l(s,this.setLanguage),s}}async setLocation(t,s){try{if(typeof t!="number"||typeof s!="number")throw new f("setLocation requires latitude and longitude to be numbers");const n=[t,s];return this.config.location=n,await this.socket.send("setLocation",{location:n,timeStamp:Date.now()})}catch(n){throw l(n,this.setLocation),n}}async openUrl(t){try{return await this.socket.send("openUrl",{url:t,timeStamp:Date.now()})}catch(s){throw l(s,this.openUrl),s}}async shake(){try{return await this.socket.send("shakeDevice")}catch(t){throw l(t,this.swipe),t}}async biometry({match:t}){try{return await this.socket.send(t?"biometryMatch":"biometryNoMatch")}catch(s){throw l(s,this.biometry),s}}async allowInteractions(t){try{return await this.socket.send(t?"enableInteractions":"disableInteractions")}catch(s){throw l(s,this.allowInteractions),s}}async restartApp(){try{this.socket.send("restartApp");const{platform:t}=this.config;t==="ios"?await this.waitForEvent("appLaunch",{timeout:6e4}):await D(1e3)}catch(t){throw l(t,this.restartApp),t}}async reinstallApp(){try{this.socket.send("reinstallApp"),await this.waitForEvent("appLaunch",{timeout:6e4})}catch(t){throw l(t,this.reinstallApp),t}}async adbShellCommand(t){if(this.config.platform!=="android")throw new Error("adbShellCommand is only available on Android devices");try{return await this.socket.send("adbShellCommand",{command:t,timeStamp:Date.now()})}catch(s){throw l(s,this.adbShellCommand),s}}async playAction(t,s={}){const{timeout:n=1e4}=s;try{if(!this.config.record)throw new O("playAction()");if(isNaN(n))throw new f(`Invalid timeout value: ${s.timeout}`);if(n<0)throw new f(`Timeout value cannot be negative: ${s.timeout}`);"element"in t&&t.element&&me(t.element);const r=new pe({platform:this.config.platform,screen:this.device.screen,app:this.app}),o=oe(),c=s.noMap?t:r.mapAction(t),h={id:o,action:c,options:{...s,timeout:Math.round(n/1e3)}},[u]=await Promise.all([new Promise((p,m)=>{const g=()=>{this.off("playbackFoundAndSent",v),this.off("playbackError",F)},v=async y=>{var R;((R=y.playback)==null?void 0:R.id)===o&&(await this.addActionLog({action:t,payload:h,result:y}),g(),p(y))},F=async y=>{var R;if(((R=y.playback)==null?void 0:R.id)===o)switch(await this.addActionLog({action:t,payload:h,error:y}),g(),y.errorId){case"internalError":m(new f(`An internal error has occurred for the action: | ||
${JSON.stringify(t,null,2)}`));break;case"notFound":if(y.playback!==void 0&&"element"in y.playback.action&&y.playback.action.element!==void 0){m(new ce(y.playback.action.element));break}case"ambiguousMatch":if("matchedElements"in y&&y.matchedElements!==void 0){m(new he(y.matchedElements));break}case"invalidArgument":if("element"in t&&t.element&&y.message.match("outside the screen bounds")){m(new ue(t));break}default:m(new f(y.message));break}};this.once("playbackFoundAndSent",v),this.once("playbackError",F)}),this.socket.send("playAction",h)]);return u}catch(r){throw l(r,this.playAction),r}}async playActions(t,s={}){try{if(!this.config.record)throw new O("playActions()");const n=[];for(const r of t){const o=await this.playAction(r,s);n.push(o);const a=t[t.indexOf(r)+1];a&&a.type==="keypress"&&r.type==="keypress"||await D(1e3)}return n}catch(n){throw l(n,this.playActions),n}}async getUI({timeout:t=3e4}={}){try{return this.socket.send("dumpUi"),await S(this,"uiDump",{timeout:t})}catch(s){throw l(s,this.getUI),s}}async findElement(t,s){var n;try{return(n=(await this.playAction({type:"findElements",element:t,appId:s==null?void 0:s.appId},s)).matchedElements)==null?void 0:n[0]}catch(r){throw l(r,this.findElement),r}}async findElements(t,s){try{return(await this.playAction({type:"findElements",element:t,appId:s==null?void 0:s.appId},s)).matchedElements}catch(n){throw l(n,this.findElements),n}}async tap(t,s){var n;try{if(!this.config.record)throw new O("tap()");return await this.playAction({type:"tap",...t,duration:((n=t.duration)!=null?n:0)/1e3},s)}catch(r){throw l(r,this.tap),r}}async swipe({duration:t,gesture:s,...n},r){try{if(!this.config.record)throw new O("swipe()");let o;const a=new ve({duration:t});if(typeof s=="function")s(a);else switch(s){case"up":a.up();break;case"down":a.down();break;case"left":a.left();break;case"right":a.right();break}if("element"in n)o={type:"swipe",element:n.element,localPosition:n.localPosition,moves:a.build()};else if("position"in n)o={type:"swipe",position:n.position,moves:a.build()};else throw new Error("Either element or position must be specified");return this.playAction(o,r)}catch(o){throw l(o,this.swipe),o}}async addActionLog(t){this.actionLogs.push(t)}async getAdbInfo(){return this.logger.warn("getAdbInfo() is deprecated. Please use the `adbConnection` property instead."),Promise.resolve(A(this,k))}async getNetworkInspectorUrl(){return this.logger.warn("getNetworkInspectorUrl() is deprecated. Please use the `networkInspectorUrl` property instead."),Promise.resolve(A(this,$))}async getDeviceInfo(){return this.logger.warn("getDeviceInfo() is deprecated. Please use the `device` property instead."),Promise.resolve(this.device)}}k=new WeakMap,$=new WeakMap;function xe(i){const e="ssh -fN -o StrictHostKeyChecking=no -oHostKeyAlgorithms=+ssh-rsa -p SERVER_PORT USERNAME@HOSTNAME -L6000:FORWARD_DESTINATION:FORWARD_PORT && adb connect localhost:6000";if(!i||!i.forwards[0])return;let t=e;return t=t.replace(/SERVER_PORT/,i.port.toString()),t=t.replace(/USERNAME/,i.user),t=t.replace(/HOSTNAME/,i.hostname),t=t.replace(/FORWARD_DESTINATION/,i.forwards[0].destination),t=t.replace(/FORWARD_PORT/,i.forwards[0].port.toString()),t}b.expect.extend({toHaveElement:async(i,e,t={})=>{try{const s=await i.findElements(e,t);return{pass:typeof t.matches=="number"?s.length===t.matches:s.length>0,message:()=>`Element not found: | ||
${JSON.stringify(e,null,2)}`}}catch(s){return{pass:!1,message:()=>s.message}}}});class ke extends w.EventEmitter{constructor({socket:e,logger:t=new U}){super(),this.logger=t,this.socket=e,this.socket.on("*",({type:s,value:n})=>{const r=Ae(s,n);r!==null&&(r?(this.emit(r.type,r.value),this.emit("*",r)):(this.emit(s,n),this.emit("*",{type:s,value:n})))})}on(e,t){return super.on(e,t)}async startSession(e){throw new Error("Not implemented")}async setConfig(e){throw new Error("Not implemented")}getConfig(){return this._config}async waitForSessionStart(e){return new Promise(async(t,s)=>{const n=()=>{s(new Error("Session disconnected before it was ready"))},r=a=>{s(new Error(`Session failed to start - ${typeof a.message=="object"?JSON.stringify(a.message):a.message}`))},o=a=>{a.message.match(/Too many requests/)&&s(new Error("Session failed to start due to too many requests"))};try{this.on("error",o),e.on("disconnect",n),e.on("error",r),await e.waitUntilReady()}finally{this.off("error",o),e.off("disconnect",n),e.off("error",r)}t(e)})}}function Ae(i,e){switch(i){case"concurrentQueue":return{type:"queue",value:{type:"concurrent",name:e.name,position:e.position}};case"queue":return{type:"queue",value:{type:"session",position:e.position}};case"userError":return{type:"error",value:e};case"newSession":return null}}class _e extends ke{constructor({socket:e,window:t,logger:s=new U,config:n}){super({socket:e,logger:s}),this.ready=!1,this.window=t,n&&(this._config=this.mapConfig(n)),this.window.on("*",async({type:r,value:o})=>{switch(r){case"app":this.app=o,this.emit(r,o);break;case"deviceInfo":this.device=o,this.emit(r,o);break;case"config":this._config=this.mapConfig(o);break}}),this.window.on("iframeload",()=>{this.ready=!1,this.session=void 0,this.init()}),this.socket.on("*",async({type:r,value:o})=>{switch(r){case"newSession":try{this.session=this.createSession(this._config,{path:o.path,token:o.sessionToken}),await this.waitForSessionStart(this.session),this.emit("session",this.session)}catch(a){this.emit("sessionError",a)}}}),this.init()}async init(){await this.window.waitUntilReady();const[e,t]=await Promise.all([this.window.postMessage({type:"getApp"},!0),this.window.postMessage({type:"getDeviceInfo"},!0),this.setConfig({record:!0,...this._config})]);this.app=e,this.device=t,this.ready=!0}async waitUntilReady(){if(!this.ready)return H(async()=>{if(!this.ready)throw new Error("Timed out waiting for client to be ready")},3e4)}async startSession(e){try{await this.waitUntilReady()}catch(s){const n=s instanceof Error?s.message:s;throw new Error(`Failed to start session. ${n}`)}this.session&&await this.session.end(),await this.setConfig(e!=null?e:{});const[t]=await Promise.all([new Promise((s,n)=>{const r=a=>{this.off("session",r),this.off("sessionError",o),s(a)},o=a=>{this.off("session",r),this.off("sessionError",o),n(a)};this.on("session",r),this.on("sessionError",o)}),this.window.postMessage({type:"requestSession"},!0)]);return t}async config(e){return this.logger.warn("client.config() is deprecated and will be removed in a future major release. Use client.setConfig() instead."),this.setConfig(e)}async setConfig({publicKey:e,...t}){if(e){const n=await this.window.postMessage({type:"loadApp",value:e},!0);if(n&&"error"in n)throw new Error(n.error)}const s=await this.window.postMessage({type:"setConfig",value:this.validateConfig(t!=null?t:{})},!0).then(this.mapConfig);return this._config=s,s}mapConfig(e){return{...e,device:e.deviceType||e.device}}validateConfig(e){return e}createSession(e,t){throw new Error("Not implemented")}}class Se extends U{constructor(){super(...arguments),this.logHistory=[],this.logLevel=process.env.CI==="true"?"warnings-errors":"verbose",this.log=(...e)=>{if(this.logHistory.push({method:"log",data:e}),this.logLevel==="verbose")return console.log(...e)},this.warn=(...e)=>{if(this.logHistory.push({method:"warn",data:e}),this.logLevel==="verbose"||this.logLevel==="warnings-errors")return console.warn(...e)},this.error=(...e)=>{if(this.logHistory.push({method:"error",data:e}),this.logLevel==="verbose"||this.logLevel==="warnings-errors")return console.error(...e)},this.debug=(...e)=>{if(this.logHistory.push({method:"debug",data:e}),this.logLevel==="verbose")return console.debug(...e)},this.clearLogHistory=()=>{this.logHistory=[]}}}const T=new Se;async function Te(i){await i.pause()}function V(i){if(i.character)return i.character;const e=i.key;return z(e)?e:i.shiftKey?e.toUpperCase():e.toLowerCase()}function z(i){return/^[\b\t\r]/.test(i)}class Pe{constructor({testInfo:e,session:t}){this.currentRecord=0,this.session=t,this.testInfo=e}async record(){const e=this.testInfo.file,t=await I.default.promises.readFile(e,"utf8"),n=t.split(` | ||
`).map((r,o)=>({line:r,num:o+1})).slice(this.testInfo.line).filter(({line:r})=>r.includes("session.record()"))[this.currentRecord].num;if(n!==void 0){console.log(`\u{1F534} Recording at line ${n}`);const r=[],o=c=>{$e(r,c),console.log(Y(c))};this.session.on("action",o),await Te(this.session.page),await D(2e3),this.session.off("action",o);const a=t.split(` | ||
`).map((c,h)=>{var u,p;if(h===n-1){const m=(p=(u=c.match(/^\s*/))==null?void 0:u[0])!=null?p:0;return`${r.map(v=>Y(v)).reduce((v,F,y)=>`${v} | ||
// ${y+1}. ${F}`,"// Recorded using session.record()")} | ||
await session.playActions(${JSON.stringify(r,null," ")})`.split(` | ||
`).map(v=>y+v).join(` | ||
`).map(v=>m+v).join(` | ||
`)}return c});await I.default.promises.writeFile(e,a.join(` | ||
`)),console.log("\u{1F7E2} Finished"),this.currentRecord+=1}}}function Pe(i,e){const t=i[i.length-1];if(t)switch(e.type){case"keypress":{(t==null?void 0:t.type)==="keypress"&&!z(e.key)&&!z(t.key)?(i.pop(),i.push({type:"typeText",text:V(t)+V(e)})):(t==null?void 0:t.type)==="typeText"&&!z(e.key)?(i.pop(),i.push({type:"typeText",text:t.text+V(e)})):i.push(e);break}default:i.push(e)}else i.push(e)}function Y(i){var t,s,n,r,o,a;let e="";switch(i.type){case"swipe":case"tap":{const c=i.element;return typeof c=="string"?e=` on element "${c}"`:(t=c==null?void 0:c.attributes)!=null&&t.accessibilityIdentifier?e=`element with accessibilityIdentifier "${(s=c.attributes)==null?void 0:s.accessibilityIdentifier}"`:(n=c==null?void 0:c.attributes)!=null&&n.class?e=`element with class "${(r=c.attributes)==null?void 0:r.class}"`:"position"in i&&((o=i.position)==null?void 0:o.x)&&((a=i.position)==null?void 0:a.y)&&(e=`position ${Math.round(i.position.x*100)}%, ${Math.round(i.position.y*100)}%`),e?`${i.type} on ${e}`:i.type}case"keypress":return`type "${V(i)}"`;case"typeText":return`type "${i.text}"`}}function Q({type:i,value:e}){switch(i){case"deviceInfo":return{type:"deviceInfo",value:e};case"sessionRequested":return{type:"sessionRequested"};case"chromeDevToolsUrl":return{type:"networkInspectorUrl",value:e};case"orientationChanged":return{type:"orientationChanged",value:e}}}const $e="1.0.2-beta.1";class Fe extends w.EventEmitter{constructor({page:e}){super(),this.ready=!1,this.page=e,this.page.exposeFunction("__appetize_on",t=>{const s=t==="string"?t:t.type;this.emit(s,t.value),this.emit("*",{type:s,value:t.value})})}async init(){this.ready=!1,await this.page.evaluate(async([e])=>new Promise((t,s)=>{const n=setTimeout(()=>{clearInterval(r),s(new C("Timed out after 60000ms waiting for connection to Appetize page"))},6e4),r=setInterval(()=>{const o=new MessageChannel;o.port1.onmessage=()=>{clearInterval(r),clearTimeout(n),t(!1)},window.postMessage({type:"init",appetizeClient:!0,version:e},"*",[o.port2]),window.__appetize_postMessage=async(a,c=!1)=>{const h=new MessageChannel;if(window.postMessage(a,"*",[h.port2]),c)return new Promise((u,p)=>{const y=setTimeout(()=>{p(new C("Timed out after 60000ms while waiting for postMessage response"))},6e4);h.port1.onmessage=g=>{clearTimeout(y),u(g.data)}})}},100)}),[$e]),await this.page.evaluate(()=>{window.__appetize__video_frames=[],window.__appetize__audio_frames=[],window.addEventListener("message",e=>{var t;if(e.source===window){const s=typeof e.data=="string"?e.data:(t=e.data)==null?void 0:t.type;if(s==="socketEvent")switch(e.data.value.type){case"h264Data":case"frameData":if(window.__appetize__video_frames.push(e.data.value),!window.__appetize_emit_video)return;break;case"audioData":if(window.__appetize__audio_frames.push(e.data.value),!window.__appetize_emit_audio)return;break}else switch(s){case"frameData":case"recordedAction":case"playbackFoundAndSent":case"playbackNotFound":case"debug":case"interceptRequest":case"interceptResponse":case"interceptError":case"uiDump":return}window.__appetize_on(e.data)}})},[]),this.ready=!0}async waitUntilReady(){return H(async()=>{if(!this.ready)throw new C("Timed out after 60000ms while waiting for Appetize window to be ready.")},6e4)}enableVideoEvents(e=!0){return this.page.evaluate(t=>{window.__appetize_emit_video=t},e)}enableAudioEvents(e=!0){return this.page.evaluate(t=>{window.__appetize_emit_audio=t},e)}async postMessage(e,t=!1){return await this.waitUntilReady(),this.page.evaluate(async([s,n])=>window.__appetize_postMessage(s,n),[e,t])}async getVideoFrames(){return this.page.evaluate(()=>window.__appetize__video_frames)}async getAudioFrames(){return this.page.evaluate(()=>window.__appetize__audio_frames)}async resetVideoFrames(){return this.page.evaluate(()=>{window.__appetize__video_frames=[]})}async resetAudioFrames(){return this.page.evaluate(()=>{window.__appetize__audio_frames=[]})}}class X extends w.EventEmitter{constructor({page:e,type:t,window:s}){super(),this.page=e,this.type=t,this.window=s,this.window.on("*",({type:n,value:r})=>{switch(n){case"socketEvent":r.socket===this.type&&(this.emit(r.type,r.value),this.emit("*",{type:r.type,value:r.value}));break;case"disconnect":this.emit("disconnect"),this.emit("*",{type:"disconnect"});break;case"sessionInfo":case"chromeDevToolsUrl":case"orientationChanged":case"deviceInfo":if(this.type==="appetizer"){const o=Q({type:n,value:r});o&&(this.emit(o.type,o.value),this.emit("*",o))}break;case"sessionRequested":if(this.type==="webserver"){const o=Q({type:n,value:r});o&&(this.emit(o.type,o.value),this.emit("*",o))}break}})}async send(e,t){return this.window.postMessage({type:"emitSocketEvent",value:{type:e,value:t,socket:this.type}})}async disconnect(){await this.send("disconnect")}waitForEvent(e,t){return _(this,e,t)}}class Ie extends ve{constructor({page:e,config:t,window:s,testInfo:n,...r}){const o=new X({page:e,window:s,type:"appetizer"});super({...r,socket:o,config:t,logger:T}),this.actionLogs=[],this.window=s,this.page=e,this.config=t,this.testInfo=n,this.page.on("load",()=>{this.emit("disconnect")}),t.record&&this.on("disconnect",()=>{this.teardownUi()})}async addActionLog(e){if(e.error){const t=await this.getUI().catch(this.logger.warn);t&&(e.ui=t)}this.actionLogs.push(e)}teardownUi(){return this.window.page.evaluate(()=>{const e=document.querySelector("app-ui");e&&e.remove()})}async rotate(e){try{const[t]=await Promise.all([_(this.window,"orientationChanged"),await this.window.postMessage(e==="left"?"rotateLeft":"rotateRight")]);return this.window.page.waitForTimeout(1e3),t}catch(t){throw l(t,this.rotate),t}}async screenshot(e="buffer"){try{const[t]=await Promise.all([_(this.socket,"screenshot",{timeout:6e4}),this.socket.send("getScreenshot")]),s=new Uint8Array(Object.values(t.data).map(Number));return{data:e==="buffer"?Buffer.from(s):t.data,mimeType:t.mimeType}}catch(t){throw l(t,this.screenshot),t}}async record(){try{if(!this.config.record)throw new f("Recording is not enabled, please enable it by setting `record: true` in session config");if(!this.testInfo)throw new f("session.record() requires using `session` from the test() arguments");return new Te({session:this,testInfo:this.testInfo}).record()}catch(e){throw l(e,this.record),e}}async waitForEvent(e,t){try{return await _(this,e,t)}catch(s){throw l(s,this.waitForEvent),s}}async waitForTimeout(e){try{return await D(e)}catch(t){throw l(t,this.waitForTimeout),t}}async waitForElement(e,t){try{const s=await this.findElements(e,t);if(s.length){if((t==null?void 0:t.matches)&&s.length!==t.matches)throw new Error(`Expected ${t.matches} elements, found ${s.length}`);return s}else throw new Error(`Element not found: | ||
${JSON.stringify(e)}`)}catch(s){throw l(s,this.waitForElement),s}}on(e,t){return e==="video"&&(q.onVideo||(this.logger.warn("Listening to the `video` event will significantly slow down your tests. It is recommended to use session.getVideoFrames() instead."),q.onVideo=!0),this.window.enableVideoEvents()),e==="audio"&&(q.onAudio||(this.logger.warn("Listening to the `audio` event will significantly slow down your tests. It is recommended to use session.getAudioFrames() instead."),q.onAudio=!0),this.window.enableAudioEvents()),super.on(e,t)}once(e,t){return e==="video"&&this.window.enableVideoEvents(),e==="audio"&&this.window.enableAudioEvents(),super.once(e,t)}async getVideoFrames(){return await this.window.getVideoFrames()}async getAudioFrames(){return await this.window.getAudioFrames()}}const q={onVideo:!1,onAudio:!1},G=new WeakMap;class Ce extends Ae{constructor({page:e}){var r;const t=(r=G.get(e))!=null?r:new Fe({page:e});G.set(e,t),t.init();const s=new X({type:"webserver",page:e,window:t});super({socket:s,window:t,logger:T}),this.window=t,this.page=e;let n=!1;this.on("queue",o=>{n||(n=!0,o.type==="concurrent"?this.logger.log(`Entered ${o.name}. Please wait until a slot becomes available.`):this.logger.log("All devices are currently in use. Please wait until requested device becomes available.")),o.position>0&&(o.type==="concurrent"?this.logger.log(`Position in ${o.name}: ${o.position}`):this.logger.log(`Position in queue: ${o.position}`))}),this.on("session",()=>{n&&(n=!1)})}validateConfig(e){var s;return{codec:((s=this.page.context().browser())==null?void 0:s.browserType().name())==="chromium"?"jpeg":"h264",...e}}createSession(e,t){return this.session=new Ie({config:e,page:this.page,window:this.window,path:t.path,token:t.token,device:this.device,app:this.app}),this.session}}class De{constructor(e){this.queue=null,this.page=e}async start(e){var r,o;const{config:t,baseURL:s}=e,n=new URLSearchParams;t.device&&n.set("device",t.device),t.deviceColor&&n.set("deviceColor",t.deviceColor),t.screenOnly&&n.set("screenOnly",t.screenOnly.toString()),t.orientation&&n.set("orientation",t.orientation),n.set("scale",(o=(r=t.scale)==null?void 0:r.toString())!=null?o:"auto"),await this.page.goto(`${s!=null?s:"https://appetize.io"}/embed/${t.publicKey}?${n.toString()}`),this.client=new Ce({page:this.page}),this.client.on("queue",a=>{this.queue=a}),await this.client.waitUntilReady(),this.session=await re(()=>this.client.startSession({...t}),{retries:5,timeout:3e4,predicate:(a,c)=>a instanceof Error&&a.message.match(/too many requests/)?(console.warn(`Too many session requests. Retrying in 30 seconds... (attempt #${c})`),!0):!1})}}let P,Z;const K=b.test.extend({_useConfig:[{},{option:!0}],config:[async({_useConfig:i},e,t)=>{await e({...t.project.use.config,...i,autoplay:!1})},{auto:!0}],_autoSnapshotSuffix:[async({},i,e)=>{e.snapshotSuffix="",await i()},{auto:!0}],appetizePage:async({browser:i,config:e},t)=>{if(P&&JSON.stringify(Z)!==JSON.stringify(e)&&(await P.page.close(),await P.page.context().close(),P=null),!P){const s=await i.newContext();if(P=new De(await s.newPage()),Z=e,!e.publicKey)throw new Error('Appetize public key not set. Make sure you have run test.use({ config: { publicKey: "..." } })')}await t(P)},session:async({appetizePage:i,config:e,baseURL:t,_doSetupAndTeardown:s},n,r)=>{if(i.session||await W.step("Start Appetize session",async()=>{await i.start({config:e,baseURL:t})}),!i.session)throw new Error("Appetize session failed to start");i.session.testInfo=r,await n(i.session)},client:async({appetizePage:i},e)=>{await e(i.client)},_doSetupAndTeardown:[async({appetizePage:i},e,t)=>{var s,n;T.clearLogHistory(),i.session&&(i.session.actionLogs=[],await i.session.window.resetVideoFrames(),await i.session.window.resetAudioFrames()),await e(),await se(i,t),t.status==="timedOut"&&((n=(s=i.queue)==null?void 0:s.position)!=null?n:0)>0&&T.warn("Test timed out while in queue for a device. You may increase your test timeout to account for higher queue times.")},{auto:!0}]}),Re=K.use,ee=K.afterEach;Object.assign(K,{setup(i){return{}.__APPETIZE__SETUP_WARNED||T.warn("test.setup() is deprecated and will be removed in a future release. Use test.use({ config: {...} }) instead"),W.use({config:i})},use(i){const{config:e,...t}=i;return Re({...t,_useConfig:e})},afterEach(i){return ee(async({appetizePage:e},t)=>{e&&await se(e,t)}),ee(i)}});const W=K,te=new WeakMap;async function se(i,e){if(te.get(e.fn))return;te.set(e.fn,!0);const t=i.session,s="fail-output";if(e.status==="failed"||e.status==="timedOut"){if(t){const o=t.actionLogs;await I.default.promises.mkdir(e.outputPath(s));const a=async()=>{try{const h=await t.screenshot("buffer");e.attach("screenshot",{body:h.data,contentType:"image/png"});const u=e.outputPath(s,"screenshot.png");await I.default.promises.writeFile(u,Buffer.from(h.data))}catch{}},c=async()=>{try{const h=o.map(y=>{if("screenshot"in y){const{screenshot:g,...v}=y;return v}return y});if(!h.length)return;const u=JSON.stringify(h,null,2);e.attach("actions",{body:u,contentType:"application/json"});const p=e.outputPath(s,"actions.json");await I.default.promises.writeFile(p,u)}catch{}};await Promise.all([a(),c()])}await(async()=>{if(!!T.logHistory.length)try{const o=`${T.logHistory.map(c=>`[${c.method}] ${JSON.stringify(c.data).slice(2,-2)}`).join(` | ||
`)),console.log("\u{1F7E2} Finished"),this.currentRecord+=1}}}function $e(i,e){const t=i[i.length-1];if(t)switch(e.type){case"keypress":{(t==null?void 0:t.type)==="keypress"&&!z(e.key)&&!z(t.key)?(i.pop(),i.push({type:"typeText",text:V(t)+V(e)})):(t==null?void 0:t.type)==="typeText"&&!z(e.key)?(i.pop(),i.push({type:"typeText",text:t.text+V(e)})):i.push(e);break}default:i.push(e)}else i.push(e)}function Y(i){var t,s,n,r,o,a;let e="";switch(i.type){case"swipe":case"tap":{const c=i.element;return typeof c=="string"?e=` on element "${c}"`:(t=c==null?void 0:c.attributes)!=null&&t.accessibilityIdentifier?e=`element with accessibilityIdentifier "${(s=c.attributes)==null?void 0:s.accessibilityIdentifier}"`:(n=c==null?void 0:c.attributes)!=null&&n.class?e=`element with class "${(r=c.attributes)==null?void 0:r.class}"`:"position"in i&&((o=i.position)==null?void 0:o.x)&&((a=i.position)==null?void 0:a.y)&&(e=`position ${Math.round(i.position.x*100)}%, ${Math.round(i.position.y*100)}%`),e?`${i.type} on ${e}`:i.type}case"keypress":return`type "${V(i)}"`;case"typeText":return`type "${i.text}"`}}function Q({type:i,value:e}){switch(i){case"deviceInfo":return{type:"deviceInfo",value:e};case"sessionRequested":return{type:"sessionRequested"};case"chromeDevToolsUrl":return{type:"networkInspectorUrl",value:e};case"orientationChanged":return{type:"orientationChanged",value:e}}}const Fe="1.0.2-beta.2";class Ie extends w.EventEmitter{constructor({page:e}){super(),this.ready=!1,this.page=e,this.page.exposeFunction("__appetize_on",t=>{const s=t==="string"?t:t.type;this.emit(s,t.value),this.emit("*",{type:s,value:t.value})})}async init(){this.ready=!1,await this.page.evaluate(async([e])=>new Promise((t,s)=>{const n=setTimeout(()=>{clearInterval(r),s(new C("Timed out after 60000ms waiting for connection to Appetize page"))},6e4),r=setInterval(()=>{const o=new MessageChannel;o.port1.onmessage=()=>{clearInterval(r),clearTimeout(n),t(!1)},window.postMessage({type:"init",appetizeClient:!0,version:e},"*",[o.port2]),window.__appetize_postMessage=async(a,c=!1)=>{const h=new MessageChannel;if(window.postMessage(a,"*",[h.port2]),c)return new Promise((u,p)=>{const m=setTimeout(()=>{p(new C("Timed out after 60000ms while waiting for postMessage response"))},6e4);h.port1.onmessage=g=>{clearTimeout(m),u(g.data)}})}},100)}),[Fe]),await this.page.evaluate(()=>{window.__appetize__video_frames=[],window.__appetize__audio_frames=[],window.addEventListener("message",e=>{var t;if(e.source===window){const s=typeof e.data=="string"?e.data:(t=e.data)==null?void 0:t.type;if(s==="socketEvent")switch(e.data.value.type){case"h264Data":case"frameData":if(window.__appetize__video_frames.push(e.data.value),!window.__appetize_emit_video)return;break;case"audioData":if(window.__appetize__audio_frames.push(e.data.value),!window.__appetize_emit_audio)return;break}else switch(s){case"frameData":case"recordedAction":case"playbackFoundAndSent":case"playbackNotFound":case"debug":case"interceptRequest":case"interceptResponse":case"interceptError":case"uiDump":return}window.__appetize_on(e.data)}})},[]),this.ready=!0}async waitUntilReady(){return H(async()=>{if(!this.ready)throw new C("Timed out after 60000ms while waiting for Appetize window to be ready.")},6e4)}enableVideoEvents(e=!0){return this.page.evaluate(t=>{window.__appetize_emit_video=t},e)}enableAudioEvents(e=!0){return this.page.evaluate(t=>{window.__appetize_emit_audio=t},e)}async postMessage(e,t=!1){return await this.waitUntilReady(),this.page.evaluate(async([s,n])=>window.__appetize_postMessage(s,n),[e,t])}async getVideoFrames(){return this.page.evaluate(()=>window.__appetize__video_frames)}async getAudioFrames(){return this.page.evaluate(()=>window.__appetize__audio_frames)}async resetVideoFrames(){return this.page.evaluate(()=>{window.__appetize__video_frames=[]})}async resetAudioFrames(){return this.page.evaluate(()=>{window.__appetize__audio_frames=[]})}}class X extends w.EventEmitter{constructor({page:e,type:t,window:s}){super(),this.page=e,this.type=t,this.window=s,this.window.on("*",({type:n,value:r})=>{switch(n){case"socketEvent":r.socket===this.type&&(this.emit(r.type,r.value),this.emit("*",{type:r.type,value:r.value}));break;case"disconnect":this.emit("disconnect"),this.emit("*",{type:"disconnect"});break;case"sessionInfo":case"chromeDevToolsUrl":case"orientationChanged":case"deviceInfo":if(this.type==="appetizer"){const o=Q({type:n,value:r});o&&(this.emit(o.type,o.value),this.emit("*",o))}break;case"sessionRequested":if(this.type==="webserver"){const o=Q({type:n,value:r});o&&(this.emit(o.type,o.value),this.emit("*",o))}break}})}async send(e,t){return this.window.postMessage({type:"emitSocketEvent",value:{type:e,value:t,socket:this.type}})}async disconnect(){await this.send("disconnect")}waitForEvent(e,t){return S(this,e,t)}}class Ce extends Ee{constructor({page:e,config:t,window:s,testInfo:n,...r}){const o=new X({page:e,window:s,type:"appetizer"});super({...r,socket:o,config:t,logger:T}),this.actionLogs=[],this.window=s,this.page=e,this.config=t,this.testInfo=n,this.page.on("load",()=>{this.emit("disconnect")}),t.record&&this.on("disconnect",()=>{this.teardownUi()})}async addActionLog(e){if(e.error){const t=await this.getUI().catch(this.logger.warn);t&&(e.ui=t)}this.actionLogs.push(e)}teardownUi(){return this.window.page.evaluate(()=>{const e=document.querySelector("app-ui");e&&e.remove()})}async rotate(e){try{const[t]=await Promise.all([S(this.window,"orientationChanged"),await this.window.postMessage(e==="left"?"rotateLeft":"rotateRight")]);return this.window.page.waitForTimeout(1e3),t}catch(t){throw l(t,this.rotate),t}}async screenshot(e="buffer"){try{const[t]=await Promise.all([S(this.socket,"screenshot",{timeout:6e4}),this.socket.send("getScreenshot")]),s=new Uint8Array(Object.values(t.data).map(Number));return{data:e==="buffer"?Buffer.from(s):t.data,mimeType:t.mimeType}}catch(t){throw l(t,this.screenshot),t}}async record(){try{if(!this.config.record)throw new f("Recording is not enabled, please enable it by setting `record: true` in session config");if(!this.testInfo)throw new f("session.record() requires using `session` from the test() arguments");return new Pe({session:this,testInfo:this.testInfo}).record()}catch(e){throw l(e,this.record),e}}async waitForEvent(e,t){try{return await S(this,e,t)}catch(s){throw l(s,this.waitForEvent),s}}async waitForTimeout(e){try{return await D(e)}catch(t){throw l(t,this.waitForTimeout),t}}async waitForElement(e,t){try{const s=await this.findElements(e,t);if(s.length){if((t==null?void 0:t.matches)&&s.length!==t.matches)throw new Error(`Expected ${t.matches} elements, found ${s.length}`);return s}else throw new Error(`Element not found: | ||
${JSON.stringify(e)}`)}catch(s){throw l(s,this.waitForElement),s}}on(e,t){return e==="video"&&(q.onVideo||(this.logger.warn("Listening to the `video` event will significantly slow down your tests. It is recommended to use session.getVideoFrames() instead."),q.onVideo=!0),this.window.enableVideoEvents()),e==="audio"&&(q.onAudio||(this.logger.warn("Listening to the `audio` event will significantly slow down your tests. It is recommended to use session.getAudioFrames() instead."),q.onAudio=!0),this.window.enableAudioEvents()),super.on(e,t)}once(e,t){return e==="video"&&this.window.enableVideoEvents(),e==="audio"&&this.window.enableAudioEvents(),super.once(e,t)}async getVideoFrames(){return await this.window.getVideoFrames()}async getAudioFrames(){return await this.window.getAudioFrames()}}const q={onVideo:!1,onAudio:!1},G=new WeakMap;class De extends _e{constructor({page:e}){var r;const t=(r=G.get(e))!=null?r:new Ie({page:e});G.set(e,t),t.init();const s=new X({type:"webserver",page:e,window:t});super({socket:s,window:t,logger:T}),this.window=t,this.page=e;let n=!1;this.on("queue",o=>{n||(n=!0,o.type==="concurrent"?this.logger.log(`Entered ${o.name}. Please wait until a slot becomes available.`):this.logger.log("All devices are currently in use. Please wait until requested device becomes available.")),o.position>0&&(o.type==="concurrent"?this.logger.log(`Position in ${o.name}: ${o.position}`):this.logger.log(`Position in queue: ${o.position}`))}),this.on("session",()=>{n&&(n=!1)})}validateConfig(e){var s;return{codec:((s=this.page.context().browser())==null?void 0:s.browserType().name())==="chromium"?"jpeg":"h264",...e}}createSession(e,t){return this.session=new Ce({config:e,page:this.page,window:this.window,path:t.path,token:t.token,device:this.device,app:this.app}),this.session}}class Re{constructor(e){this.queue=null,this.page=e}async start(e){const{config:t,baseURL:s}=e,{publicKey:n,...r}=t;await this.page.goto(`${s!=null?s:"https://appetize.io"}/embed/${n}?${ae(r)}`),this.client=new De({page:this.page}),this.client.on("queue",o=>{this.queue=o}),await this.client.waitUntilReady(),this.session=await re(()=>this.client.startSession({...t}),{retries:5,timeout:3e4,predicate:(o,a)=>o instanceof Error&&o.message.match(/too many requests/)?(console.warn(`Too many session requests. Retrying in 30 seconds... (attempt #${a})`),!0):!1})}}let P,Z;const K=b.test.extend({_useConfig:[{},{option:!0}],config:[async({_useConfig:i},e,t)=>{await e({...t.project.use.config,...i,autoplay:!1})},{auto:!0}],_autoSnapshotSuffix:[async({},i,e)=>{e.snapshotSuffix="",await i()},{auto:!0}],appetizePage:async({browser:i,config:e},t)=>{if(P&&JSON.stringify(Z)!==JSON.stringify(e)&&(await P.page.close(),await P.page.context().close(),P=null),!P){const s=await i.newContext();if(P=new Re(await s.newPage()),Z=e,!e.publicKey)throw new Error('Appetize public key not set. Make sure you have run test.use({ config: { publicKey: "..." } })')}await t(P)},session:async({appetizePage:i,config:e,baseURL:t,_doSetupAndTeardown:s},n,r)=>{if(i.session||await W.step("Start Appetize session",async()=>{await i.start({config:e,baseURL:t})}),!i.session)throw new Error("Appetize session failed to start");i.session.testInfo=r,await n(i.session)},client:async({appetizePage:i},e)=>{await e(i.client)},_doSetupAndTeardown:[async({appetizePage:i},e,t)=>{var s,n;T.clearLogHistory(),i.session&&(i.session.actionLogs=[],await i.session.window.resetVideoFrames(),await i.session.window.resetAudioFrames()),await e(),await se(i,t),t.status==="timedOut"&&((n=(s=i.queue)==null?void 0:s.position)!=null?n:0)>0&&T.warn("Test timed out while in queue for a device. You may increase your test timeout to account for higher queue times.")},{auto:!0}]}),Me=K.use,ee=K.afterEach;Object.assign(K,{setup(i){return{}.__APPETIZE__SETUP_WARNED||T.warn("test.setup() is deprecated and will be removed in a future release. Use test.use({ config: {...} }) instead"),W.use({config:i})},use(i){const{config:e,...t}=i;return Me({...t,_useConfig:e})},afterEach(i){return ee(async({appetizePage:e},t)=>{e&&await se(e,t)}),ee(i)}});const W=K,te=new WeakMap;async function se(i,e){if(te.get(e.fn))return;te.set(e.fn,!0);const t=i.session,s="fail-output";if(e.status==="failed"||e.status==="timedOut"){if(t){const o=t.actionLogs;await I.default.promises.mkdir(e.outputPath(s));const a=async()=>{try{const h=await t.screenshot("buffer");e.attach("screenshot",{body:h.data,contentType:"image/png"});const u=e.outputPath(s,"screenshot.png");await I.default.promises.writeFile(u,Buffer.from(h.data))}catch{}},c=async()=>{try{const h=o.map(m=>{if("screenshot"in m){const{screenshot:g,...v}=m;return v}return m});if(!h.length)return;const u=JSON.stringify(h,null,2);e.attach("actions",{body:u,contentType:"application/json"});const p=e.outputPath(s,"actions.json");await I.default.promises.writeFile(p,u)}catch{}};await Promise.all([a(),c()])}await(async()=>{if(!!T.logHistory.length)try{const o=`${T.logHistory.map(c=>`[${c.method}] ${JSON.stringify(c.data).slice(2,-2)}`).join(` | ||
`)}`;e.attach("sdk-logs",{body:o,contentType:"application/text"});const a=e.outputPath(s,"sdk-logs.txt");await I.default.promises.writeFile(a,o)}catch{}})()}}Object.defineProperty(d,"expect",{enumerable:!0,get:function(){return b.expect}}),d.test=W,Object.defineProperties(d,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}); | ||
//# sourceMappingURL=index.umd.js.map |
@@ -25,5 +25,5 @@ import { Page } from '@playwright/test'; | ||
proxy?: string | undefined; | ||
device: string; | ||
device?: string | undefined; | ||
locale?: string | undefined; | ||
osVersion: string; | ||
osVersion?: string | undefined; | ||
adbShellCommand?: string | undefined; | ||
@@ -30,0 +30,0 @@ androidPackageManager?: boolean | undefined; |
{ | ||
"name": "@appetize/playwright", | ||
"version": "1.0.2-beta.1", | ||
"version": "1.0.2-beta.2", | ||
"description": "Test your mobile apps on Appetize.io with Playwright", | ||
@@ -5,0 +5,0 @@ "files": [ |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
557602
4230