@appetize/playwright
Advanced tools
Comparing version 0.1.0 to 0.1.1
/// <reference types="node" /> | ||
/// <reference types="node" /> | ||
import { EventEmitter } from 'events'; | ||
import { WaitForEventOptions } from '../core/util'; | ||
import { WaitForEventOptions } from '../core/waitFor'; | ||
import { Action, ActionPlaybackResult, Element } from '../core/actions'; | ||
@@ -6,0 +6,0 @@ import { SocketProtocol } from './socket'; |
@@ -1,10 +0,6 @@ | ||
/// <reference types="node" /> | ||
import { EventEmitter } from 'events'; | ||
export declare function waitFor<T>(fn: () => T | Promise<T>, timeout?: number | null): Promise<T>; | ||
export declare function waitForTimeout(ms: number): Promise<unknown>; | ||
export interface WaitForEventOptions<T> { | ||
timeout?: number | null; | ||
predicate?: (data: T) => boolean; | ||
} | ||
export declare function waitForEvent<T>(emitter: EventEmitter, event: string, options?: WaitForEventOptions<T>): Promise<T>; | ||
export declare function retry<T>(fn: () => T | Promise<T>, { retries, timeout, predicate, }: { | ||
retries?: number; | ||
timeout?: number; | ||
predicate?: (e: unknown, attempt: number) => boolean | undefined; | ||
}): Promise<T>; | ||
export declare function convertLegacyEvent(event: string, data: any): { | ||
@@ -11,0 +7,0 @@ type: string; |
@@ -5,41 +5,143 @@ import { expect, test as test$1 } from "@playwright/test"; | ||
import fs from "fs"; | ||
async function waitFor(fn, timeout = 5e3) { | ||
const start = Date.now(); | ||
while (true) { | ||
class SwipeGesture { | ||
constructor(args) { | ||
this.path = []; | ||
this.easing = (t) => t; | ||
this.steps = 20; | ||
this.stepDuration = 25; | ||
if (args == null ? void 0 : args.easing) { | ||
if (typeof args.easing === "string") { | ||
this.easing = Easings[args.easing]; | ||
} else { | ||
this.easing = args.easing; | ||
} | ||
} | ||
if (args == null ? void 0 : args.steps) { | ||
this.steps = args.steps; | ||
} | ||
if (args == null ? void 0 : args.stepDuration) { | ||
this.stepDuration = args.stepDuration; | ||
} | ||
} | ||
from(xOrElement, y) { | ||
if (typeof xOrElement === "object") { | ||
this.element = xOrElement; | ||
this.path[0] = { type: "move", x: 0, y: 0 }; | ||
} else { | ||
this.path[0] = { type: "move", x: xOrElement, y }; | ||
} | ||
return this; | ||
} | ||
to(x, y, relative = true) { | ||
if (relative) { | ||
this.path.push({ | ||
type: "move", | ||
x, | ||
y | ||
}); | ||
} else { | ||
this.path.push({ type: "move", x, y }); | ||
} | ||
return this; | ||
} | ||
wait(duration) { | ||
this.path.push({ type: "wait", value: duration }); | ||
return this; | ||
} | ||
toAction() { | ||
const interpolated = this.interpolatePath(); | ||
return { | ||
type: "swipe", | ||
xPos: interpolated.map((p) => p.x.toString()), | ||
yPos: interpolated.map((p) => p.y.toString()), | ||
ts: interpolated.map( | ||
(_, i) => this.easing(i * (this.stepDuration / 1e3)) | ||
), | ||
element: this.element | ||
}; | ||
} | ||
previousCommand(path, type) { | ||
return path.slice().reverse().find((p) => p.type === type); | ||
} | ||
interpolatePath() { | ||
const interpolatedPath = []; | ||
for (let i = 0; i < this.path.length - 1; i++) { | ||
const previousMove = this.previousCommand( | ||
this.path.slice(0, i + 1), | ||
"move" | ||
); | ||
const start = previousMove; | ||
const end = this.path[i + 1]; | ||
if (start) { | ||
if (end.type === "move") { | ||
const xUnit = getSwipeUnit(end.x) || getSwipeUnit(start.x); | ||
const yUnit = getSwipeUnit(end.y) || getSwipeUnit(start.y); | ||
const numSteps = Math.floor( | ||
this.steps / (this.path.length - 1) | ||
); | ||
const stepX = (getSwipeValue(end.x) - getSwipeValue(start.x)) / numSteps; | ||
const stepY = (getSwipeValue(end.y) - getSwipeValue(start.y)) / numSteps; | ||
const canInterpolateX = getSwipeUnit(end.x) === getSwipeUnit(start.x) || getSwipeValue(end.x) === 0 || getSwipeValue(start.x) === 0; | ||
const canInterpolateY = getSwipeUnit(end.y) === getSwipeUnit(start.y) || getSwipeValue(end.y) === 0 || getSwipeValue(start.y) === 0; | ||
for (let j = 0; j <= numSteps; j++) { | ||
interpolatedPath.push({ | ||
x: canInterpolateX ? `${getSwipeValue(start.x) + stepX * j}${xUnit}` : start.x, | ||
y: canInterpolateY ? `${getSwipeValue(start.y) + stepY * j}${yUnit}` : start.y | ||
}); | ||
} | ||
} else if (end.type === "wait") { | ||
const amt = Math.floor(end.value / this.stepDuration); | ||
for (let j = 0; j <= amt; j++) { | ||
interpolatedPath.push({ | ||
x: start.x, | ||
y: start.y | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
if (interpolatedPath.length) { | ||
interpolatedPath.unshift(interpolatedPath[0]); | ||
interpolatedPath.push( | ||
interpolatedPath[interpolatedPath.length - 1] | ||
); | ||
} | ||
return interpolatedPath; | ||
} | ||
} | ||
const getSwipeUnit = (n) => { | ||
if (typeof n === "number" || n.endsWith("px")) { | ||
return ""; | ||
} | ||
return n.replace(/^-?\d+/, ""); | ||
}; | ||
const getSwipeValue = (n) => { | ||
if (typeof n === "number") { | ||
return n; | ||
} | ||
return parseInt(n); | ||
}; | ||
const Easings = { | ||
linear: (t) => t, | ||
easeIn: (t) => t * t, | ||
easeOut: (t) => t * (2 - t), | ||
easeInOut: (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t | ||
}; | ||
async function retry(fn, { | ||
retries = 3, | ||
timeout = 1e3, | ||
predicate = () => true | ||
}) { | ||
for (let i = 1; i <= retries; i++) { | ||
try { | ||
const result = await fn(); | ||
return result; | ||
return await fn(); | ||
} catch (e) { | ||
await new Promise((resolve) => setTimeout(resolve, 100)); | ||
if (timeout !== null && Date.now() - start > timeout) { | ||
if (i === retries || !predicate(e, i)) { | ||
throw e; | ||
} | ||
await new Promise((resolve) => setTimeout(resolve, timeout)); | ||
} | ||
} | ||
throw null; | ||
} | ||
async function waitForTimeout(ms) { | ||
return new Promise((resolve) => setTimeout(resolve, ms)); | ||
} | ||
async function waitForEvent(emitter, event, options = {}) { | ||
const { timeout = 1e4, predicate = () => true } = options; | ||
return new Promise((resolve, reject) => { | ||
const listener = (data) => { | ||
if (predicate(data)) { | ||
emitter.off(event, listener); | ||
resolve(data); | ||
} | ||
}; | ||
emitter.on(event, listener); | ||
if (timeout !== null) { | ||
setTimeout(() => { | ||
emitter.off(event, listener); | ||
reject( | ||
new Error( | ||
`Timed out after ${timeout}ms waiting for "${event}" event` | ||
) | ||
); | ||
}, timeout); | ||
} | ||
}); | ||
} | ||
function convertLegacyEvent(event, data) { | ||
@@ -147,2 +249,44 @@ switch (event) { | ||
} | ||
async function waitFor(fn, timeout = 5e3) { | ||
const start = Date.now(); | ||
while (true) { | ||
try { | ||
const result = await fn(); | ||
return result; | ||
} catch (e) { | ||
await new Promise((resolve) => setTimeout(resolve, 100)); | ||
if (timeout !== null && Date.now() - start > timeout) { | ||
throw e; | ||
} | ||
} | ||
} | ||
} | ||
async function waitForTimeout(ms) { | ||
return new Promise((resolve) => setTimeout(resolve, ms)); | ||
} | ||
async function waitForEvent(emitter, event, optionsOrPredicate) { | ||
var _a; | ||
const options = typeof optionsOrPredicate === "function" ? {} : optionsOrPredicate; | ||
const predicate = typeof optionsOrPredicate === "function" ? optionsOrPredicate : optionsOrPredicate == null ? void 0 : optionsOrPredicate.predicate; | ||
const timeout = (_a = options == null ? void 0 : options.timeout) != null ? _a : 1e4; | ||
return new Promise((resolve, reject) => { | ||
const listener = (data) => { | ||
if (!predicate || predicate(data)) { | ||
emitter.off(event, listener); | ||
resolve(data); | ||
} | ||
}; | ||
emitter.on(event, listener); | ||
if (timeout !== null) { | ||
setTimeout(() => { | ||
emitter.off(event, listener); | ||
reject( | ||
new Error( | ||
`Timed out after ${timeout}ms waiting for "${event}" event` | ||
) | ||
); | ||
}, timeout); | ||
} | ||
}); | ||
} | ||
function uint8ArrayToString(uint8Arr) { | ||
@@ -168,126 +312,2 @@ const length = uint8Arr.length; | ||
} | ||
class SwipeGesture { | ||
constructor(args) { | ||
this.path = []; | ||
this.easing = (t) => t; | ||
this.steps = 20; | ||
this.stepDuration = 25; | ||
if (args == null ? void 0 : args.easing) { | ||
if (typeof args.easing === "string") { | ||
this.easing = Easings[args.easing]; | ||
} else { | ||
this.easing = args.easing; | ||
} | ||
} | ||
if (args == null ? void 0 : args.steps) { | ||
this.steps = args.steps; | ||
} | ||
if (args == null ? void 0 : args.stepDuration) { | ||
this.stepDuration = args.stepDuration; | ||
} | ||
} | ||
from(xOrElement, y) { | ||
if (typeof xOrElement === "object") { | ||
this.element = xOrElement; | ||
this.path[0] = { type: "move", x: 0, y: 0 }; | ||
} else { | ||
this.path[0] = { type: "move", x: xOrElement, y }; | ||
} | ||
return this; | ||
} | ||
to(x, y, relative = true) { | ||
if (relative) { | ||
this.path.push({ | ||
type: "move", | ||
x, | ||
y | ||
}); | ||
} else { | ||
this.path.push({ type: "move", x, y }); | ||
} | ||
return this; | ||
} | ||
wait(duration) { | ||
this.path.push({ type: "wait", value: duration }); | ||
return this; | ||
} | ||
toAction() { | ||
const interpolated = this.interpolatePath(); | ||
return { | ||
type: "swipe", | ||
xPos: interpolated.map((p) => p.x.toString()), | ||
yPos: interpolated.map((p) => p.y.toString()), | ||
ts: interpolated.map( | ||
(_, i) => this.easing(i * (this.stepDuration / 1e3)) | ||
), | ||
element: this.element | ||
}; | ||
} | ||
previousCommand(path, type) { | ||
return path.slice().reverse().find((p) => p.type === type); | ||
} | ||
interpolatePath() { | ||
const interpolatedPath = []; | ||
for (let i = 0; i < this.path.length - 1; i++) { | ||
const previousMove = this.previousCommand( | ||
this.path.slice(0, i + 1), | ||
"move" | ||
); | ||
const start = previousMove; | ||
const end = this.path[i + 1]; | ||
if (start) { | ||
if (end.type === "move") { | ||
const xUnit = getSwipeUnit(end.x) || getSwipeUnit(start.x); | ||
const yUnit = getSwipeUnit(end.y) || getSwipeUnit(start.y); | ||
const numSteps = Math.floor( | ||
this.steps / (this.path.length - 1) | ||
); | ||
const stepX = (getSwipeValue(end.x) - getSwipeValue(start.x)) / numSteps; | ||
const stepY = (getSwipeValue(end.y) - getSwipeValue(start.y)) / numSteps; | ||
const canInterpolateX = getSwipeUnit(end.x) === getSwipeUnit(start.x) || getSwipeValue(end.x) === 0 || getSwipeValue(start.x) === 0; | ||
const canInterpolateY = getSwipeUnit(end.y) === getSwipeUnit(start.y) || getSwipeValue(end.y) === 0 || getSwipeValue(start.y) === 0; | ||
for (let j = 0; j <= numSteps; j++) { | ||
interpolatedPath.push({ | ||
x: canInterpolateX ? `${getSwipeValue(start.x) + stepX * j}${xUnit}` : start.x, | ||
y: canInterpolateY ? `${getSwipeValue(start.y) + stepY * j}${yUnit}` : start.y | ||
}); | ||
} | ||
} else if (end.type === "wait") { | ||
const amt = Math.floor(end.value / this.stepDuration); | ||
for (let j = 0; j <= amt; j++) { | ||
interpolatedPath.push({ | ||
x: start.x, | ||
y: start.y | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
if (interpolatedPath.length) { | ||
interpolatedPath.unshift(interpolatedPath[0]); | ||
interpolatedPath.push( | ||
interpolatedPath[interpolatedPath.length - 1] | ||
); | ||
} | ||
return interpolatedPath; | ||
} | ||
} | ||
const getSwipeUnit = (n) => { | ||
if (typeof n === "number" || n.endsWith("px")) { | ||
return ""; | ||
} | ||
return n.replace(/^-?\d+/, ""); | ||
}; | ||
const getSwipeValue = (n) => { | ||
if (typeof n === "number") { | ||
return n; | ||
} | ||
return parseInt(n); | ||
}; | ||
const Easings = { | ||
linear: (t) => t, | ||
easeIn: (t) => t * t, | ||
easeOut: (t) => t * (2 - t), | ||
easeInOut: (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t | ||
}; | ||
const digitCharsWithShift = [ | ||
@@ -1351,2 +1371,18 @@ ")", | ||
} | ||
async waitForEvent(event, options) { | ||
return waitForEvent(this.socket, event, options); | ||
} | ||
async waitForTimeout(ms) { | ||
return waitForTimeout(ms); | ||
} | ||
async waitForElement(element, options) { | ||
const elements = await this.findElements(element, options); | ||
const pass = typeof (options == null ? void 0 : options.matches) === "number" ? elements.length === options.matches : elements.length > 0; | ||
if (!pass) { | ||
throw new Error(`Element not found: | ||
${JSON.stringify(element)}`); | ||
} else { | ||
return elements; | ||
} | ||
} | ||
} | ||
@@ -1525,3 +1561,4 @@ class Client extends EventEmitter { | ||
pass, | ||
message: () => `Element not found: ${JSON.stringify(selector)}` | ||
message: () => `Element not found: | ||
${JSON.stringify(selector)}` | ||
}; | ||
@@ -1576,11 +1613,10 @@ } catch (e) { | ||
test.afterEach(async ({ session: session2 }, testInfo) => { | ||
if (testInfo.error) { | ||
console.log( | ||
"actionHistory", | ||
JSON.stringify(session2.actionHistory, null, 2) | ||
); | ||
} | ||
}); | ||
test.beforeAll(async ({ browser, baseURL }, testInfo) => { | ||
var _a, _b; | ||
if (testInfo.config.fullyParallel) { | ||
throw new Error( | ||
`fullyParallel is not allowed when running Appetize tests. Please set \`fullyParallel: false\` in your Playwright config` | ||
); | ||
} | ||
test.setTimeout(6e4 * 5); | ||
@@ -1616,6 +1652,21 @@ if (!page) { | ||
}); | ||
session = await client.startSession({ | ||
...options, | ||
testInfo | ||
}); | ||
session = await retry( | ||
() => client.startSession({ | ||
...options, | ||
testInfo | ||
}), | ||
{ | ||
retries: 5, | ||
timeout: 3e4, | ||
predicate: (e, attempt) => { | ||
if (e instanceof Error && e.message.match(/too many requests/)) { | ||
console.warn( | ||
`Too many session requests. Retrying in 30 seconds... (attempt #${attempt})` | ||
); | ||
return true; | ||
} | ||
return false; | ||
} | ||
} | ||
); | ||
}); | ||
@@ -1629,6 +1680,3 @@ test.afterAll(async ({ session: session2 }, testInfo) => { | ||
const test = _test; | ||
function getClient(page2) { | ||
return new PlaywrightClient({ page: page2 }); | ||
} | ||
export { SwipeGesture, getClient, getSwipeUnit, getSwipeValue, test }; | ||
export { SwipeGesture, getSwipeUnit, getSwipeValue, test }; | ||
//# sourceMappingURL=index.es.js.map |
@@ -1,5 +0,5 @@ | ||
(function(p,k){typeof exports=="object"&&typeof module!="undefined"?k(exports,require("@playwright/test"),require("events"),require("fs")):typeof define=="function"&&define.amd?define(["exports","@playwright/test","events","fs"],k):(p=typeof globalThis!="undefined"?globalThis:p||self,k(p.playwright={},p.test$1,p.events,p.fs))})(this,function(p,k,I,z){"use strict";function j(s){return s&&typeof s=="object"&&"default"in s?s:{default:s}}var R=j(z);async function C(s,e=5e3){const t=Date.now();for(;;)try{return await s()}catch(n){if(await new Promise(i=>setTimeout(i,100)),e!==null&&Date.now()-t>e)throw n}}async function T(s){return new Promise(e=>setTimeout(e,s))}async function m(s,e,t={}){const{timeout:n=1e4,predicate:i=()=>!0}=t;return new Promise((o,r)=>{const c=a=>{i(a)&&(s.off(e,c),o(a))};s.on(e,c),n!==null&&setTimeout(()=>{s.off(e,c),r(new Error(`Timed out after ${n}ms waiting for "${e}" event`))},n)})}function O(s,e){switch(s){case"accountQueuedPosition":case"accountQueue":return{type:"queue",value:{type:"account",position:e.position}};case"sessionQueuedPosition":case"queue":return{type:"queue",value:{type:"session",position:e.position}};case"debug":return{type:"log",value:e};case"interceptResponse":return{type:"network",value:{type:"response",...e}};case"interceptRequest":return{type:"network",value:{type:"request",...e}};case"interceptError":return{type:"network",value:{type:"error",...e}};case"userError":return{type:"error",value:e};case"recordedEvent":return{type:"action",value:e};case"deleteEvent":return{type:"deletedAction",value:e};case"userInteractionReceived":return{type:"interaction",value:e};case"countdownWarning":return{type:"inactivityWarning",value:e};case"h264Data":return{type:"video",value:{...e,codec:"h264"}};case"frameData":return{type:"video",value:{...e,codec:"jpeg"}};case"audioData":return{type:"audio",value:{...e,codec:"aac"}}}}function W(s){const e=s.length;let t="";for(let n=0;n<e;n+=65535){let i=65535;n+65535>e&&(i=e-n),t+=String.fromCharCode.apply(null,s.subarray(n,n+i))}return t}function H(s,e){const t=W(s),n=btoa(t);return`data:${e};base64,`+n}class D{constructor(e){this.path=[],this.easing=t=>t,this.steps=20,this.stepDuration=25,e!=null&&e.easing&&(typeof e.easing=="string"?this.easing=V[e.easing]:this.easing=e.easing),e!=null&&e.steps&&(this.steps=e.steps),e!=null&&e.stepDuration&&(this.stepDuration=e.stepDuration)}from(e,t){return typeof e=="object"?(this.element=e,this.path[0]={type:"move",x:0,y:0}):this.path[0]={type:"move",x:e,y:t},this}to(e,t,n=!0){return n?this.path.push({type:"move",x:e,y:t}):this.path.push({type:"move",x:e,y:t}),this}wait(e){return this.path.push({type:"wait",value:e}),this}toAction(){const e=this.interpolatePath();return{type:"swipe",xPos:e.map(t=>t.x.toString()),yPos:e.map(t=>t.y.toString()),ts:e.map((t,n)=>this.easing(n*(this.stepDuration/1e3))),element:this.element}}previousCommand(e,t){return e.slice().reverse().find(n=>n.type===t)}interpolatePath(){const e=[];for(let t=0;t<this.path.length-1;t++){const i=this.previousCommand(this.path.slice(0,t+1),"move"),o=this.path[t+1];if(i){if(o.type==="move"){const r=y(o.x)||y(i.x),c=y(o.y)||y(i.y),a=Math.floor(this.steps/(this.path.length-1)),u=(h(o.x)-h(i.x))/a,l=(h(o.y)-h(i.y))/a,d=y(o.x)===y(i.x)||h(o.x)===0||h(i.x)===0,f=y(o.y)===y(i.y)||h(o.y)===0||h(i.y)===0;for(let w=0;w<=a;w++)e.push({x:d?`${h(i.x)+u*w}${r}`:i.x,y:f?`${h(i.y)+l*w}${c}`:i.y})}else if(o.type==="wait"){const r=Math.floor(o.value/this.stepDuration);for(let c=0;c<=r;c++)e.push({x:i.x,y:i.y})}}}return e.length&&(e.unshift(e[0]),e.push(e[e.length-1])),e}}const y=s=>typeof s=="number"||s.endsWith("px")?"":s.replace(/^-?\d+/,""),h=s=>typeof s=="number"?s:parseInt(s),V={linear:s=>s,easeIn:s=>s*s,easeOut:s=>s*(2-s),easeInOut:s=>s<.5?2*s*s:-1+(4-2*s)*s},U=[")","!","@","#","$","%","^","&","*","("],_={47:"?",44:"<",45:"_",46:">",91:"{",92:"|",93:"}",96:"~",59:":",61:"+",39:'"'},Y={191:"?",188:"<",189:"_",190:">",219:"{",220:"|",221:"}",192:"~",186:":",187:"+",222:'"'};function J(s){let e;for(const n in _)if(s===_[n])return e={key:String.fromCharCode(n),shiftKey:"true"},e;const t=U.indexOf(s);return t>-1?e={key:String.fromCharCode(t+48).toLowerCase(),shiftKey:!0}:s!==s.toLowerCase()?e={key:s.toLowerCase(),shiftKey:!0}:e={key:s,shiftKey:!1},e}function $(s){return s.type==="keypress"&&s.which&&s.key?s.which>=65&&s.which<=90?s.shiftKey?s.key:s.key.toLowerCase():s.shiftKey?s.which>=48&&s.which<=57?U[s.which-48]:Y[s.which]:s.key:null}function B(s){switch(s){case"HOME":return"home";case"VOLUME_UP":return"volumeUp";case"VOLUME_DOWN":return"volumeDown"}return s}class Q extends Error{constructor(e){var i,o,r,c;let t=e.message;const n=(i=e.playback)==null?void 0:i.event;switch(e.errorId){case"unknown":t=`${e.message}`;break;case"notFound":if(n&&"element"in n){let a=!1;const u=typeof n.element=="object"?{...n.element}:n.element;typeof u=="object"&&"allowMultipleMatches"in u&&(a=!!u.allowMultipleMatches,delete u.allowMultipleMatches),t=`No element${a?"s":""} found for selector | ||
${JSON.stringify(u,null," ")}`}break;case"ambiguousMatch":{const a=(o=e.matchedElements)==null?void 0:o.map(({frame:u,address:l,frameInWindow:d,bounds:f,windowType:w,...x})=>x);a&&(t=`More than 1 element matched the selector. Please specify more attributes to narrow down the matches to a single element, or provide a \`matchIndex\` attribute to select one of the following results | ||
(function(d,k){typeof exports=="object"&&typeof module!="undefined"?k(exports,require("@playwright/test"),require("events"),require("fs")):typeof define=="function"&&define.amd?define(["exports","@playwright/test","events","fs"],k):(d=typeof globalThis!="undefined"?globalThis:d||self,k(d.playwright={},d.test$1,d.events,d.fs))})(this,function(d,k,$,L){"use strict";function z(s){return s&&typeof s=="object"&&"default"in s?s:{default:s}}var R=z(L);class C{constructor(e){this.path=[],this.easing=t=>t,this.steps=20,this.stepDuration=25,e!=null&&e.easing&&(typeof e.easing=="string"?this.easing=j[e.easing]:this.easing=e.easing),e!=null&&e.steps&&(this.steps=e.steps),e!=null&&e.stepDuration&&(this.stepDuration=e.stepDuration)}from(e,t){return typeof e=="object"?(this.element=e,this.path[0]={type:"move",x:0,y:0}):this.path[0]={type:"move",x:e,y:t},this}to(e,t,n=!0){return n?this.path.push({type:"move",x:e,y:t}):this.path.push({type:"move",x:e,y:t}),this}wait(e){return this.path.push({type:"wait",value:e}),this}toAction(){const e=this.interpolatePath();return{type:"swipe",xPos:e.map(t=>t.x.toString()),yPos:e.map(t=>t.y.toString()),ts:e.map((t,n)=>this.easing(n*(this.stepDuration/1e3))),element:this.element}}previousCommand(e,t){return e.slice().reverse().find(n=>n.type===t)}interpolatePath(){const e=[];for(let t=0;t<this.path.length-1;t++){const i=this.previousCommand(this.path.slice(0,t+1),"move"),o=this.path[t+1];if(i){if(o.type==="move"){const r=y(o.x)||y(i.x),c=y(o.y)||y(i.y),a=Math.floor(this.steps/(this.path.length-1)),u=(h(o.x)-h(i.x))/a,l=(h(o.y)-h(i.y))/a,p=y(o.x)===y(i.x)||h(o.x)===0||h(i.x)===0,f=y(o.y)===y(i.y)||h(o.y)===0||h(i.y)===0;for(let w=0;w<=a;w++)e.push({x:p?`${h(i.x)+u*w}${r}`:i.x,y:f?`${h(i.y)+l*w}${c}`:i.y})}else if(o.type==="wait"){const r=Math.floor(o.value/this.stepDuration);for(let c=0;c<=r;c++)e.push({x:i.x,y:i.y})}}}return e.length&&(e.unshift(e[0]),e.push(e[e.length-1])),e}}const y=s=>typeof s=="number"||s.endsWith("px")?"":s.replace(/^-?\d+/,""),h=s=>typeof s=="number"?s:parseInt(s),j={linear:s=>s,easeIn:s=>s*s,easeOut:s=>s*(2-s),easeInOut:s=>s<.5?2*s*s:-1+(4-2*s)*s};async function W(s,{retries:e=3,timeout:t=1e3,predicate:n=()=>!0}){for(let i=1;i<=e;i++)try{return await s()}catch(o){if(i===e||!n(o,i))throw o;await new Promise(r=>setTimeout(r,t))}throw null}function U(s,e){switch(s){case"accountQueuedPosition":case"accountQueue":return{type:"queue",value:{type:"account",position:e.position}};case"sessionQueuedPosition":case"queue":return{type:"queue",value:{type:"session",position:e.position}};case"debug":return{type:"log",value:e};case"interceptResponse":return{type:"network",value:{type:"response",...e}};case"interceptRequest":return{type:"network",value:{type:"request",...e}};case"interceptError":return{type:"network",value:{type:"error",...e}};case"userError":return{type:"error",value:e};case"recordedEvent":return{type:"action",value:e};case"deleteEvent":return{type:"deletedAction",value:e};case"userInteractionReceived":return{type:"interaction",value:e};case"countdownWarning":return{type:"inactivityWarning",value:e};case"h264Data":return{type:"video",value:{...e,codec:"h264"}};case"frameData":return{type:"video",value:{...e,codec:"jpeg"}};case"audioData":return{type:"audio",value:{...e,codec:"aac"}}}}async function D(s,e=5e3){const t=Date.now();for(;;)try{return await s()}catch(n){if(await new Promise(i=>setTimeout(i,100)),e!==null&&Date.now()-t>e)throw n}}async function I(s){return new Promise(e=>setTimeout(e,s))}async function m(s,e,t){var r;const n=typeof t=="function"?{}:t,i=typeof t=="function"?t:t==null?void 0:t.predicate,o=(r=n==null?void 0:n.timeout)!=null?r:1e4;return new Promise((c,a)=>{const u=l=>{(!i||i(l))&&(s.off(e,u),c(l))};s.on(e,u),o!==null&&setTimeout(()=>{s.off(e,u),a(new Error(`Timed out after ${o}ms waiting for "${e}" event`))},o)})}function H(s){const e=s.length;let t="";for(let n=0;n<e;n+=65535){let i=65535;n+65535>e&&(i=e-n),t+=String.fromCharCode.apply(null,s.subarray(n,n+i))}return t}function V(s,e){const t=H(s),n=btoa(t);return`data:${e};base64,`+n}const F=[")","!","@","#","$","%","^","&","*","("],_={47:"?",44:"<",45:"_",46:">",91:"{",92:"|",93:"}",96:"~",59:":",61:"+",39:'"'},Y={191:"?",188:"<",189:"_",190:">",219:"{",220:"|",221:"}",192:"~",186:":",187:"+",222:'"'};function J(s){let e;for(const n in _)if(s===_[n])return e={key:String.fromCharCode(n),shiftKey:"true"},e;const t=F.indexOf(s);return t>-1?e={key:String.fromCharCode(t+48).toLowerCase(),shiftKey:!0}:s!==s.toLowerCase()?e={key:s.toLowerCase(),shiftKey:!0}:e={key:s,shiftKey:!1},e}function M(s){return s.type==="keypress"&&s.which&&s.key?s.which>=65&&s.which<=90?s.shiftKey?s.key:s.key.toLowerCase():s.shiftKey?s.which>=48&&s.which<=57?F[s.which-48]:Y[s.which]:s.key:null}function B(s){switch(s){case"HOME":return"home";case"VOLUME_UP":return"volumeUp";case"VOLUME_DOWN":return"volumeDown"}return s}class Q extends Error{constructor(e){var i,o,r,c;let t=e.message;const n=(i=e.playback)==null?void 0:i.event;switch(e.errorId){case"unknown":t=`${e.message}`;break;case"notFound":if(n&&"element"in n){let a=!1;const u=typeof n.element=="object"?{...n.element}:n.element;typeof u=="object"&&"allowMultipleMatches"in u&&(a=!!u.allowMultipleMatches,delete u.allowMultipleMatches),t=`No element${a?"s":""} found for selector | ||
${JSON.stringify(u,null," ")}`}break;case"ambiguousMatch":{const a=(o=e.matchedElements)==null?void 0:o.map(({frame:u,address:l,frameInWindow:p,bounds:f,windowType:w,...x})=>x);a&&(t=`More than 1 element matched the selector. Please specify more attributes to narrow down the matches to a single element, or provide a \`matchIndex\` attribute to select one of the following results | ||
${a.map((u,l)=>`${l}: ${JSON.stringify(u,null," ")}`).join(`, | ||
`)}`);break}default:{const a=e.playback;a!=null&&a.event.id?t=`Action (id: "${a==null?void 0:a.event.id}") failed: ${(r=e.message)!=null?r:e.errorId}`:t=`Action (type: "${a==null?void 0:a.event.type}") failed: ${(c=e.message)!=null?c:e.errorId}`}}super(t),this.name="PlayActionError"}}class M extends Error{constructor(e){super(`App Recorder must be enabled to use ${e}. Please set "record" to true in the config.`),this.name="RecorderRequiredError"}}class X extends I.EventEmitter{constructor({socket:e,config:t}){super(),this.actionHistory=[],this.isEnding=!1,this.debug={printActions:({xdoc:i=!1}={})=>{console.log(this.actionHistory.reduce((o,{action:r})=>i?`let actions = ${JSON.stringify(this.actionHistory.map(c=>c.action),null,2)} | ||
`)}`);break}default:{const a=e.playback;a!=null&&a.event.id?t=`Action (id: "${a==null?void 0:a.event.id}") failed: ${(r=e.message)!=null?r:e.errorId}`:t=`Action (type: "${a==null?void 0:a.event.type}") failed: ${(c=e.message)!=null?c:e.errorId}`}}super(t),this.name="PlayActionError"}}class P extends Error{constructor(e){super(`App Recorder must be enabled to use ${e}. Please set "record" to true in the config.`),this.name="RecorderRequiredError"}}class X extends $.EventEmitter{constructor({socket:e,config:t}){super(),this.actionHistory=[],this.isEnding=!1,this.debug={printActions:({xdoc:i=!1}={})=>{console.log(this.actionHistory.reduce((o,{action:r})=>i?`let actions = ${JSON.stringify(this.actionHistory.map(c=>c.action),null,2)} | ||
@@ -22,3 +22,3 @@ let nextAction = actions.shift() | ||
`:" "+o+` | ||
`+JSON.stringify(r,null,2),""))}},this.config=t,this.socket=e;const n=({type:i,value:o})=>{const r=O(i,o);switch(i){case"adbOverTcp":{this.adbConnectionInfo={...o,command:G(o)};break}case"networkInspectorUrl":this.networkInspectorUrl=o;break}r?(this.emit(r.type,r.value),this.emit("*",r)):(this.emit(i,o),this.emit("*",{type:i,value:o}))};this.socket.on("*",n),this.on("disconnect",()=>{if(this.socket.off("*",n),!this.isEnding)throw new Error("Session disconnected unexpectedly")})}on(e,t){return e==="network"&&this.config.proxy!=="intercept"&&console.warn(`Session must be configured to use a proxy in order to listen to network events. You can do this with | ||
`+JSON.stringify(r,null,2),""))}},this.config=t,this.socket=e;const n=({type:i,value:o})=>{const r=U(i,o);switch(i){case"adbOverTcp":{this.adbConnectionInfo={...o,command:G(o)};break}case"networkInspectorUrl":this.networkInspectorUrl=o;break}r?(this.emit(r.type,r.value),this.emit("*",r)):(this.emit(i,o),this.emit("*",{type:i,value:o}))};this.socket.on("*",n),this.on("disconnect",()=>{if(this.socket.off("*",n),!this.isEnding)throw new Error("Session disconnected unexpectedly")})}on(e,t){return e==="network"&&this.config.proxy!=="intercept"&&console.warn(`Session must be configured to use a proxy in order to listen to network events. You can do this with | ||
@@ -31,7 +31,7 @@ startSession({ proxy: "intercept" })`),e==="log"&&this.config.debug!==!0&&console.warn(`Session must be configured to use debug mode in order to listen to log events. You can do this with | ||
startSession({ proxy: "intercept" })`);return this.networkInspectorUrl?this.networkInspectorUrl:C(()=>{if(this.networkInspectorUrl)return this.networkInspectorUrl;throw new Error("Timed out waiting for network inspector URL")})}async getAdbInfo(){if(this.config.platform&&this.config.platform!=="android")throw new Error("Session must be connected to an Android device to use adb");if(!this.config.enableAdb)throw new Error(`Session must be configured to use adb in order to get the adb command. You can do this with | ||
startSession({ proxy: "intercept" })`);return this.networkInspectorUrl?this.networkInspectorUrl:D(()=>{if(this.networkInspectorUrl)return this.networkInspectorUrl;throw new Error("Timed out waiting for network inspector URL")})}async getAdbInfo(){if(this.config.platform&&this.config.platform!=="android")throw new Error("Session must be connected to an Android device to use adb");if(!this.config.enableAdb)throw new Error(`Session must be configured to use adb in order to get the adb command. You can do this with | ||
startSession({ enableAdb: true })`);return this.adbConnectionInfo?this.adbConnectionInfo:C(()=>{if(this.adbConnectionInfo)return this.adbConnectionInfo;throw new Error("Timed out waiting for adb connection")})}async rotate(e){const[t]=await Promise.all([this.waitForEvent("orientationChanged"),this.socket.send("userInteraction",{type:"keypress",key:e==="left"?"rotateLeft":"rotateRight",timeStamp:Date.now()})]);return t}async screenshot(e="buffer"){this.socket.send("getScreenshot");const t=await m(this.socket,"screenshot",{timeout:6e4});if(!t.success)throw new Error("Screenshot failed");return{data:e==="buffer"?(o=>typeof window=="undefined"?Buffer.from(o):o)(t.data):H(new Uint8Array(t.data),t.mimeType),mimeType:t.mimeType}}async heartbeat(){return this.socket.send("heartbeat")}async type(e){this.config.platform==="ios"&&await T(1e3);const t=[...e].map(J);if(this.config.record)return this.playAction({type:"keypress",keypress:{type:"keypressArray",shiftKeyArray:t.map(n=>n.shiftKey),keyArray:t.map(n=>n.key),value:e}});await Promise.all([this.socket.send("userInteraction",{type:"keypressArray",shiftKeyArray:t.map(n=>n.shiftKey),keyArray:t.map(n=>n.key),value:e}),this.waitForEvent("interaction",{predicate:n=>n.type==="keypressArray"})])}async keypress(e,t){if(e==="ANDROID_KEYCODE_MENU")return this.socket.send("androidKeycodeMenu");e=B(e);const n=Date.now(),[i]=await Promise.all([this.waitForEvent("interaction",{predicate:o=>o.type==="keypress"&&o.timeStamp===n}),this.socket.send("userInteraction",{type:"keypress",key:e,timeStamp:n,...t})]);return i}async setLanguage(e){this.config.language=e,await this.socket.send("setLanguage",{language:e,timeStamp:Date.now()})}async setLocation(e,t){const n=[e.toString(),t.toString()];return this.config.location=n,this.socket.send("setLocation",{location:n,timeStamp:Date.now()})}async openUrl(e){return this.socket.send("openUrl",{url:e,timeStamp:Date.now()})}async shake(){return this.socket.send("shakeDevice")}async biometry({match:e}){return this.socket.send(e?"biometryMatch":"biometryNoMatch")}async allowInteractions(e){return this.socket.send(e?"enableInteractions":"disableInteractions")}async restartApp(){this.socket.send("restartApp");const{platform:e}=this.config;e==="ios"?await this.waitForEvent("appLaunch",{timeout:6e4}):await T(1e3)}async reinstallApp(){this.socket.send("reinstallApp"),await this.waitForEvent("appLaunch",{timeout:6e4})}async playAction(e,t={}){if(!this.config.record)throw new M("playAction()");return this.playActions([e],t).then(n=>n[0])}async playActions(e,t={}){const{timeout:n=3e4,strictMatch:i}=t,o=[];if(!this.config.record)throw new M("playActions()");for(const r of e){const{id:c,...a}=r;let u=a.type;switch(u){case"tap":u="click";break}const[l]=await Promise.all([new Promise((d,f)=>{const w=()=>{this.off("playbackFoundAndSent",x),this.off("playbackError",v)},x=async b=>{this.actionHistory.push({action:r,result:b}),w(),d(b)},v=b=>{this.actionHistory.push({action:r,error:b}),w(),f(new Q(b))};this.once("playbackFoundAndSent",x),this.once("playbackError",v)}),this.socket.send("playEvent",{event:{...a,type:u},timeout:Math.round(n/1e3),strictMatch:i,id:c})]);o.push(l)}return o}async getUI({timeout:e=3e4}={}){this.socket.send("dumpUi");const t=await m(this.socket,"uiDump",{timeout:e});return"ui"in t?t.ui:t.result}async findElement(e,{timeout:t,...n}={}){const i=await this.playAction({type:"assert",element:e},{timeout:t,...n});if(i.matchedElements)return i.matchedElements[0];if(i.matchedElement)return i.matchedElement}async findElements(e,t){const n=await this.playAction({type:"assert",element:{...e,allowMultipleMatches:!0}},t);return n.matchedElements?n.matchedElements:n.matchedElement?[n.matchedElement]:[]}async tap(e){if("element"in e){if(!this.config.record)throw new M('"element" selector');return this.playAction({type:"click",element:e.element})}await this.socket.send("userInteraction",{type:"mousedown",timeStamp:Date.now(),xPos:e.x,yPos:e.y}).then(()=>{this.socket.send("userInteraction",{type:"mouseup",timeStamp:Date.now(),xPos:e.x,yPos:e.y})}),await this.waitForEvent("interaction",{predicate:t=>t.type==="mouseup"})}async swipe(e){if(!this.config.record)throw new M("swipe()");let t;if(e instanceof D)t=e.toAction();else{const{duration:n=300,direction:i}=e;let o=e.distance;const r=new D({steps:25,stepDuration:n/25});if("element"in e){o||(o=i==="up"||i==="down"?"50vh":"50vw"),r.from(e.element);const c=h(o),a=y(o),u=i==="up"||i==="left"?-1:1;switch(i){case"up":case"down":r.to(0,u*c+a);break;case"left":case"right":r.to(u*c+a,0);break}}else{const c=h(e.x),a=y(e.x),u=h(e.y),l=y(e.y);o||(i==="left"||i==="right"?a?o=50+a:o=300:l?o=50+l:o=300);const d=h(o),f=y(o);if((i==="up"||i==="down")&&l!==f)throw new Error(`Distance unit (${f||"px"}) does not match y unit (${l||"px"})`);if((i==="left"||i==="right")&&a!==f)throw new Error(`Distance unit (${f||"px"}) does not match x unit (${a||"px"})`);switch(r.from(e.x,e.y),i){case"up":r.to(e.x,`${u-d}${l}`);break;case"down":r.to(e.x,`${u+d}${l}`);break;case"left":r.to(`${c-d}${a}`,e.y);break;case"right":r.to(`${c+d}${a}`,e.y);break}}t=r.toAction()}return this.playAction(t)}}function G(s){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(!s||!s.forwards[0])return;let t=e;return t=t.replace(/SERVER_PORT/,s.port.toString()),t=t.replace(/USERNAME/,s.user),t=t.replace(/HOSTNAME/,s.hostname),t=t.replace(/FORWARD_DESTINATION/,s.forwards[0].destination),t=t.replace(/FORWARD_PORT/,s.forwards[0].port.toString()),t}function Z({type:s,value:e}){switch(s){case"chromeDevToolsUrl":return{type:"networkInspectorUrl",value:e};case"orientationChanged":return{type:"orientationChanged",value:e}}}const F="0.1.4";class ee extends I.EventEmitter{constructor({page:e}){super(),this.ready=!1,this.page=e,this.page.exposeFunction("__appetize_on",t=>{const n=t==="string"?t:t.type;this.emit(n,t.value),this.emit("*",{type:n,value:t.value})})}async init(){this.ready=!1,await this.page.evaluate(async([e])=>new Promise(t=>{const n=setTimeout(()=>{throw new Error("Unable to find Appetize device on page (timed out after 60 seconds)")},6e4),i=setInterval(()=>{const o=new MessageChannel;o.port1.onmessage=()=>{clearInterval(i),clearTimeout(n),t(!1)},window.postMessage({type:"init",appetizeClient:!0,version:e},"*",[o.port2]),window.__appetize_postMessage=async(r,c=!1)=>{const a=new MessageChannel;if(window.postMessage(r,"*",[a.port2]),c)return new Promise((u,l)=>{const d=setTimeout(()=>{l(new Error("Timed out waiting for postMessage response"))},6e4);a.port1.onmessage=f=>{clearTimeout(d),u(f.data)}})}},100)}),[F]),await this.page.evaluate(()=>{window.addEventListener("message",e=>{e.source===window&&window.__appetize_on(e.data)})},[]),this.ready=!0}async waitUntilReady(){return C(async()=>{if(!this.ready)throw new Error("Timed out waiting for Appetize window to be ready.")},3e4)}async postMessage(e,t=!1){return await this.waitUntilReady(),this.page.evaluate(async([n,i])=>window.__appetize_postMessage(n,i),[e,t])}}class N extends I.EventEmitter{constructor({page:e,type:t,window:n}){super(),this.page=e,this.type=t,this.window=n,this.window.on("*",({type:i,value:o})=>{switch(i){case"socketEvent":o.socket===this.type&&(this.emit(o.type,o.value),this.emit("*",{type:o.type,value:o.value}));break;case"disconnect":this.emit("disconnect"),this.emit("*",{type:"disconnect"});break;case"sessionInfo":case"chromeDevToolsUrl":case"orientationChanged":if(this.type==="appetizer"){const r=Z({type:i,value:o});r&&(this.emit(r.type,r.value),this.emit("*",r))}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 m(this,e,t)}}async function te(s){await s.pause()}const g=function(){const s="[Appetize]";return Function.prototype.bind.call(console.log,console,s)}();class se{constructor({testInfo:e,session:t}){this.currentRecord=0,this.session=t,this.testInfo=e}async record(){const e=this.testInfo.file,t=await R.default.promises.readFile(e,"utf8"),i=t.split(` | ||
`).map((o,r)=>({line:o,num:r+1})).slice(this.testInfo.line).filter(({line:o})=>o.includes("session.record()"))[this.currentRecord].num;if(i!==void 0){g(`\u{1F534} Recording at line ${i}`);let o=[];const r=u=>{ie(u),ne(o,u),g(K(u))},c=u=>{o=o.filter(l=>l.id!==u.id)};this.session.on("action",r),this.session.on("deletedAction",c),await te(this.session.page),await T(2e3),this.session.off("action",r);const a=t.split(` | ||
`).map((u,l)=>{var d,f;if(l===i-1){const w=(f=(d=u.match(/^\s*/))==null?void 0:d[0])!=null?f:0;return`${o.map(v=>K(v)).reduce((v,b,ce)=>`${v} | ||
startSession({ enableAdb: true })`);return this.adbConnectionInfo?this.adbConnectionInfo:D(()=>{if(this.adbConnectionInfo)return this.adbConnectionInfo;throw new Error("Timed out waiting for adb connection")})}async rotate(e){const[t]=await Promise.all([this.waitForEvent("orientationChanged"),this.socket.send("userInteraction",{type:"keypress",key:e==="left"?"rotateLeft":"rotateRight",timeStamp:Date.now()})]);return t}async screenshot(e="buffer"){this.socket.send("getScreenshot");const t=await m(this.socket,"screenshot",{timeout:6e4});if(!t.success)throw new Error("Screenshot failed");return{data:e==="buffer"?(o=>typeof window=="undefined"?Buffer.from(o):o)(t.data):V(new Uint8Array(t.data),t.mimeType),mimeType:t.mimeType}}async heartbeat(){return this.socket.send("heartbeat")}async type(e){this.config.platform==="ios"&&await I(1e3);const t=[...e].map(J);if(this.config.record)return this.playAction({type:"keypress",keypress:{type:"keypressArray",shiftKeyArray:t.map(n=>n.shiftKey),keyArray:t.map(n=>n.key),value:e}});await Promise.all([this.socket.send("userInteraction",{type:"keypressArray",shiftKeyArray:t.map(n=>n.shiftKey),keyArray:t.map(n=>n.key),value:e}),this.waitForEvent("interaction",{predicate:n=>n.type==="keypressArray"})])}async keypress(e,t){if(e==="ANDROID_KEYCODE_MENU")return this.socket.send("androidKeycodeMenu");e=B(e);const n=Date.now(),[i]=await Promise.all([this.waitForEvent("interaction",{predicate:o=>o.type==="keypress"&&o.timeStamp===n}),this.socket.send("userInteraction",{type:"keypress",key:e,timeStamp:n,...t})]);return i}async setLanguage(e){this.config.language=e,await this.socket.send("setLanguage",{language:e,timeStamp:Date.now()})}async setLocation(e,t){const n=[e.toString(),t.toString()];return this.config.location=n,this.socket.send("setLocation",{location:n,timeStamp:Date.now()})}async openUrl(e){return this.socket.send("openUrl",{url:e,timeStamp:Date.now()})}async shake(){return this.socket.send("shakeDevice")}async biometry({match:e}){return this.socket.send(e?"biometryMatch":"biometryNoMatch")}async allowInteractions(e){return this.socket.send(e?"enableInteractions":"disableInteractions")}async restartApp(){this.socket.send("restartApp");const{platform:e}=this.config;e==="ios"?await this.waitForEvent("appLaunch",{timeout:6e4}):await I(1e3)}async reinstallApp(){this.socket.send("reinstallApp"),await this.waitForEvent("appLaunch",{timeout:6e4})}async playAction(e,t={}){if(!this.config.record)throw new P("playAction()");return this.playActions([e],t).then(n=>n[0])}async playActions(e,t={}){const{timeout:n=3e4,strictMatch:i}=t,o=[];if(!this.config.record)throw new P("playActions()");for(const r of e){const{id:c,...a}=r;let u=a.type;switch(u){case"tap":u="click";break}const[l]=await Promise.all([new Promise((p,f)=>{const w=()=>{this.off("playbackFoundAndSent",x),this.off("playbackError",v)},x=async b=>{this.actionHistory.push({action:r,result:b}),w(),p(b)},v=b=>{this.actionHistory.push({action:r,error:b}),w(),f(new Q(b))};this.once("playbackFoundAndSent",x),this.once("playbackError",v)}),this.socket.send("playEvent",{event:{...a,type:u},timeout:Math.round(n/1e3),strictMatch:i,id:c})]);o.push(l)}return o}async getUI({timeout:e=3e4}={}){this.socket.send("dumpUi");const t=await m(this.socket,"uiDump",{timeout:e});return"ui"in t?t.ui:t.result}async findElement(e,{timeout:t,...n}={}){const i=await this.playAction({type:"assert",element:e},{timeout:t,...n});if(i.matchedElements)return i.matchedElements[0];if(i.matchedElement)return i.matchedElement}async findElements(e,t){const n=await this.playAction({type:"assert",element:{...e,allowMultipleMatches:!0}},t);return n.matchedElements?n.matchedElements:n.matchedElement?[n.matchedElement]:[]}async tap(e){if("element"in e){if(!this.config.record)throw new P('"element" selector');return this.playAction({type:"click",element:e.element})}await this.socket.send("userInteraction",{type:"mousedown",timeStamp:Date.now(),xPos:e.x,yPos:e.y}).then(()=>{this.socket.send("userInteraction",{type:"mouseup",timeStamp:Date.now(),xPos:e.x,yPos:e.y})}),await this.waitForEvent("interaction",{predicate:t=>t.type==="mouseup"})}async swipe(e){if(!this.config.record)throw new P("swipe()");let t;if(e instanceof C)t=e.toAction();else{const{duration:n=300,direction:i}=e;let o=e.distance;const r=new C({steps:25,stepDuration:n/25});if("element"in e){o||(o=i==="up"||i==="down"?"50vh":"50vw"),r.from(e.element);const c=h(o),a=y(o),u=i==="up"||i==="left"?-1:1;switch(i){case"up":case"down":r.to(0,u*c+a);break;case"left":case"right":r.to(u*c+a,0);break}}else{const c=h(e.x),a=y(e.x),u=h(e.y),l=y(e.y);o||(i==="left"||i==="right"?a?o=50+a:o=300:l?o=50+l:o=300);const p=h(o),f=y(o);if((i==="up"||i==="down")&&l!==f)throw new Error(`Distance unit (${f||"px"}) does not match y unit (${l||"px"})`);if((i==="left"||i==="right")&&a!==f)throw new Error(`Distance unit (${f||"px"}) does not match x unit (${a||"px"})`);switch(r.from(e.x,e.y),i){case"up":r.to(e.x,`${u-p}${l}`);break;case"down":r.to(e.x,`${u+p}${l}`);break;case"left":r.to(`${c-p}${a}`,e.y);break;case"right":r.to(`${c+p}${a}`,e.y);break}}t=r.toAction()}return this.playAction(t)}}function G(s){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(!s||!s.forwards[0])return;let t=e;return t=t.replace(/SERVER_PORT/,s.port.toString()),t=t.replace(/USERNAME/,s.user),t=t.replace(/HOSTNAME/,s.hostname),t=t.replace(/FORWARD_DESTINATION/,s.forwards[0].destination),t=t.replace(/FORWARD_PORT/,s.forwards[0].port.toString()),t}function Z({type:s,value:e}){switch(s){case"chromeDevToolsUrl":return{type:"networkInspectorUrl",value:e};case"orientationChanged":return{type:"orientationChanged",value:e}}}const O="0.1.4";class ee extends $.EventEmitter{constructor({page:e}){super(),this.ready=!1,this.page=e,this.page.exposeFunction("__appetize_on",t=>{const n=t==="string"?t:t.type;this.emit(n,t.value),this.emit("*",{type:n,value:t.value})})}async init(){this.ready=!1,await this.page.evaluate(async([e])=>new Promise(t=>{const n=setTimeout(()=>{throw new Error("Unable to find Appetize device on page (timed out after 60 seconds)")},6e4),i=setInterval(()=>{const o=new MessageChannel;o.port1.onmessage=()=>{clearInterval(i),clearTimeout(n),t(!1)},window.postMessage({type:"init",appetizeClient:!0,version:e},"*",[o.port2]),window.__appetize_postMessage=async(r,c=!1)=>{const a=new MessageChannel;if(window.postMessage(r,"*",[a.port2]),c)return new Promise((u,l)=>{const p=setTimeout(()=>{l(new Error("Timed out waiting for postMessage response"))},6e4);a.port1.onmessage=f=>{clearTimeout(p),u(f.data)}})}},100)}),[O]),await this.page.evaluate(()=>{window.addEventListener("message",e=>{e.source===window&&window.__appetize_on(e.data)})},[]),this.ready=!0}async waitUntilReady(){return D(async()=>{if(!this.ready)throw new Error("Timed out waiting for Appetize window to be ready.")},3e4)}async postMessage(e,t=!1){return await this.waitUntilReady(),this.page.evaluate(async([n,i])=>window.__appetize_postMessage(n,i),[e,t])}}class N extends $.EventEmitter{constructor({page:e,type:t,window:n}){super(),this.page=e,this.type=t,this.window=n,this.window.on("*",({type:i,value:o})=>{switch(i){case"socketEvent":o.socket===this.type&&(this.emit(o.type,o.value),this.emit("*",{type:o.type,value:o.value}));break;case"disconnect":this.emit("disconnect"),this.emit("*",{type:"disconnect"});break;case"sessionInfo":case"chromeDevToolsUrl":case"orientationChanged":if(this.type==="appetizer"){const r=Z({type:i,value:o});r&&(this.emit(r.type,r.value),this.emit("*",r))}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 m(this,e,t)}}async function te(s){await s.pause()}const g=function(){const s="[Appetize]";return Function.prototype.bind.call(console.log,console,s)}();class se{constructor({testInfo:e,session:t}){this.currentRecord=0,this.session=t,this.testInfo=e}async record(){const e=this.testInfo.file,t=await R.default.promises.readFile(e,"utf8"),i=t.split(` | ||
`).map((o,r)=>({line:o,num:r+1})).slice(this.testInfo.line).filter(({line:o})=>o.includes("session.record()"))[this.currentRecord].num;if(i!==void 0){g(`\u{1F534} Recording at line ${i}`);let o=[];const r=u=>{ie(u),ne(o,u),g(K(u))},c=u=>{o=o.filter(l=>l.id!==u.id)};this.session.on("action",r),this.session.on("deletedAction",c),await te(this.session.page),await I(2e3),this.session.off("action",r);const a=t.split(` | ||
`).map((u,l)=>{var p,f;if(l===i-1){const w=(f=(p=u.match(/^\s*/))==null?void 0:p[0])!=null?f:0;return`${o.map(v=>K(v)).reduce((v,b,ce)=>`${v} | ||
// ${ce+1}. ${b}`,"// Recorded using session.record()")} | ||
@@ -41,3 +41,5 @@ await session.playActions(${JSON.stringify(o,null," ")})`.split(` | ||
`)}return u});await R.default.promises.writeFile(e,a.join(` | ||
`)),g("\u{1F7E2} Finished"),this.currentRecord+=1}}}function ne(s,e){const t=s[s.length-1];if(t)switch(e.type){case"keypress":(t==null?void 0:t.type)==="keypress"&&e.type==="keypress"&&e.keypress.type==="keypress"?t.keypress.type==="keypress"?s[s.length-1]={...t,keypress:{type:"keypressArray",shiftKeyArray:[t.keypress.shiftKey,e.keypress.shiftKey],keyArray:[t.keypress.key,e.keypress.key],value:$(t.keypress)+$(e.keypress)}}:s[s.length-1]={...t,keypress:{type:"keypressArray",shiftKeyArray:[...t.keypress.shiftKeyArray,e.keypress.shiftKey],keyArray:[...t.keypress.keyArray,e.keypress.key],value:t.keypress.value+$(e.keypress)}}:s.push(e);break;default:s.push(e)}else s.push(e)}function K(s){let e="";switch(s.type){case"swipe":case"click":{const t=s.element;return typeof t=="string"?e=` on element "${t}"`:t!=null&&t.accessibilityIdentifier?e=`element with accessibilityIdentifier "${t.accessibilityIdentifier}"`:t!=null&&t.class?e=`element with class "${t.class}"`:s.xPos&&s.yPos&&(e=`position ${s.xPos}, ${s.yPos}`),e?`${s.type} on ${e}`:s.type}case"keypress":return s.keypress.type==="keypress"?`type "${$(s.keypress)}"`:`type "${s.keypress.value}"`;default:return s.type}}function ie(s){switch(s.type){case"click":case"swipe":delete s.ui;break}return s}class oe extends X{constructor({page:e,config:t,window:n,testInfo:i}){const o=new N({page:e,window:n,type:"appetizer"});super({socket:o,config:t}),this.window=n,this.page=e,this.config=t,this.testInfo=i,this.page.on("load",()=>{this.emit("disconnect")}),t.record&&this.on("disconnect",()=>{this.teardownUi()})}async getUI(e={}){return await super.getUI(e)}teardownUi(){return this.window.page.evaluate(()=>{const e=document.querySelector("app-ui");e&&e.remove()})}async rotate(e){const[t]=await Promise.all([m(this.window,"orientationChanged"),await this.window.postMessage(e==="left"?"rotateLeft":"rotateRight")]);return this.window.page.waitForTimeout(1e3),t}async screenshot(e="buffer"){const[t]=await Promise.all([m(this.socket,"screenshot",{timeout:6e4}),this.socket.send("getScreenshot")]),n=new Uint8Array(Object.values(t.data).map(Number));return{data:e==="buffer"?Buffer.from(n):t.data,mimeType:t.mimeType}}async record(){if(!this.config.record)throw new Error("Recording is not enabled, please enable it by setting `record: true` in session config");if(!this.testInfo)throw new Error("session.record() requires using `session` from the test() arguments");return new se({session:this,testInfo:this.testInfo}).record()}}class re extends I.EventEmitter{constructor({socket:e}){super(),this.socket=e,this.socket.on("*",({type:t,value:n})=>{const i=O(t,n);if(i)this.emit(i.type,i.value),this.emit("*",i);else switch(t){case"newSession":break;default:this.emit(t,n),this.emit("*",{type:t,value:n})}}),this.socket.on("disconnect",()=>{throw new Error("Client disconnected unexpectedly")})}on(e,t){return super.on(e,t)}async startSession(e){throw new Error("Not implemented")}async config(e){throw new Error("Not implemented")}async waitForSessionStart(e){return new Promise(async(t,n)=>{const i=()=>{n(new Error("Session failed to start - client disconnected"))},o=c=>{n(new Error(`Session failed to start - ${typeof c.message=="object"?JSON.stringify(c.message):c.message}`))},r=c=>{c.message.match(/Too many requests/)&&n(new Error("Session failed to start - too many requests"))};try{this.on("error",r),e.on("disconnect",i),e.on("error",o),await e.waitForEvent("ready",{timeout:null})}finally{this.off("error",r),e.off("disconnect",i),e.off("error",o)}t(e)})}}const L=new WeakMap;class q extends re{constructor({page:e}){var o;const t=(o=L.get(e))!=null?o:new ee({page:e});L.set(e,t),t.init();const n=new N({type:"webserver",page:e,window:t});super({socket:n}),this.page=e,this.window=t,this.window.on("*",({type:r,value:c})=>{switch(r){case"config":this.currentConfig=c;break}});let i=!1;this.on("queue",({type:r,position:c})=>{r==="account"&&i!=="account"?(i="account",g("All slots for this account are currently in use. Please wait until a slot becomes available.")):(i==="account"?g("You are now in the regular queue."):i||g("All devices are currently in use. Please wait until requested device becomes available."),i="session"),c>0&&g(`Position in queue: ${c}`)}),this.on("session",()=>{i&&(g("Session is ready to start"),i=!1)})}async startSession({testInfo:e,...t}={}){var r;await this.window.waitUntilReady();const n=(r=this.page.context().browser())==null?void 0:r.browserType().name(),i=await this.config({codec:n==="chromium"?"jpeg":"h264",record:!0,...t,apiVersion:F}),o=new oe({config:i,page:this.page,window:this.window,testInfo:e});return this.window.postMessage({type:"requestSession"}),await this.waitForSessionStart(o),o}async config({publicKey:e,...t}){return e&&await Promise.all([this.window.postMessage({type:"loadApp",value:e}),m(this.window,"app",{predicate(i){return i.publicKey===e}})]),this.window.postMessage({type:"setConfig",value:t}),await m(this.window,"config")}}k.expect.extend({toHaveElement:async(s,e,t={})=>{try{const n=await s.findElements(e,t);return{pass:typeof t.matches=="number"?n.length===t.matches:n.length>0,message:()=>`Element not found: ${JSON.stringify(e)}`}}catch(n){return{pass:!1,message:()=>n.message}}}});let S,E,P;const A=Object.assign(k.test.extend({_autoSnapshotSuffix:[async({},s,e)=>{e.snapshotSuffix="",await s()},{auto:!0}],page:async({},s)=>{if(!E)throw new Error("Appetize not initialized. Make sure you have run test.setup()");await s(E.page)},session:async({},s)=>{if(!P)throw new Error("Session was not started. Make sure you have run test.setup()");await s(P)},client:async({},s)=>{if(!E)throw new Error("Appetize not initialized. Make sure you have run test.setup()");await s(E)}}),{setup(s){A.afterEach(async({session:e},t)=>{t.error&&console.log("actionHistory",JSON.stringify(e.actionHistory,null,2))}),A.beforeAll(async({browser:e,baseURL:t},n)=>{var i,o;if(A.setTimeout(6e4*5),S||(S=await e.newPage(),S.on("close",()=>{S=null,E=null,P=null})),"url"in s)await S.goto(s.url);else{const r=new URLSearchParams;s.device&&r.set("device",s.device),s.deviceColor&&r.set("deviceColor",s.deviceColor),s.screenOnly&&r.set("screenOnly",s.screenOnly.toString()),r.set("scale",(o=(i=s.scale)==null?void 0:i.toString())!=null?o:"auto"),await S.goto(`${t!=null?t:"https://appetize.io"}/embed/${s.publicKey}?${r.toString()}`)}E=new q({page:S}),P=await E.startSession({...s,testInfo:n})}),A.afterAll(async({session:e},t)=>{await e.page.close()})}});function ae(s){return new q({page:s})}Object.defineProperty(p,"expect",{enumerable:!0,get:function(){return k.expect}}),p.SwipeGesture=D,p.getClient=ae,p.getSwipeUnit=y,p.getSwipeValue=h,p.test=A,Object.defineProperties(p,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}); | ||
`)),g("\u{1F7E2} Finished"),this.currentRecord+=1}}}function ne(s,e){const t=s[s.length-1];if(t)switch(e.type){case"keypress":(t==null?void 0:t.type)==="keypress"&&e.type==="keypress"&&e.keypress.type==="keypress"?t.keypress.type==="keypress"?s[s.length-1]={...t,keypress:{type:"keypressArray",shiftKeyArray:[t.keypress.shiftKey,e.keypress.shiftKey],keyArray:[t.keypress.key,e.keypress.key],value:M(t.keypress)+M(e.keypress)}}:s[s.length-1]={...t,keypress:{type:"keypressArray",shiftKeyArray:[...t.keypress.shiftKeyArray,e.keypress.shiftKey],keyArray:[...t.keypress.keyArray,e.keypress.key],value:t.keypress.value+M(e.keypress)}}:s.push(e);break;default:s.push(e)}else s.push(e)}function K(s){let e="";switch(s.type){case"swipe":case"click":{const t=s.element;return typeof t=="string"?e=` on element "${t}"`:t!=null&&t.accessibilityIdentifier?e=`element with accessibilityIdentifier "${t.accessibilityIdentifier}"`:t!=null&&t.class?e=`element with class "${t.class}"`:s.xPos&&s.yPos&&(e=`position ${s.xPos}, ${s.yPos}`),e?`${s.type} on ${e}`:s.type}case"keypress":return s.keypress.type==="keypress"?`type "${M(s.keypress)}"`:`type "${s.keypress.value}"`;default:return s.type}}function ie(s){switch(s.type){case"click":case"swipe":delete s.ui;break}return s}class oe extends X{constructor({page:e,config:t,window:n,testInfo:i}){const o=new N({page:e,window:n,type:"appetizer"});super({socket:o,config:t}),this.window=n,this.page=e,this.config=t,this.testInfo=i,this.page.on("load",()=>{this.emit("disconnect")}),t.record&&this.on("disconnect",()=>{this.teardownUi()})}async getUI(e={}){return await super.getUI(e)}teardownUi(){return this.window.page.evaluate(()=>{const e=document.querySelector("app-ui");e&&e.remove()})}async rotate(e){const[t]=await Promise.all([m(this.window,"orientationChanged"),await this.window.postMessage(e==="left"?"rotateLeft":"rotateRight")]);return this.window.page.waitForTimeout(1e3),t}async screenshot(e="buffer"){const[t]=await Promise.all([m(this.socket,"screenshot",{timeout:6e4}),this.socket.send("getScreenshot")]),n=new Uint8Array(Object.values(t.data).map(Number));return{data:e==="buffer"?Buffer.from(n):t.data,mimeType:t.mimeType}}async record(){if(!this.config.record)throw new Error("Recording is not enabled, please enable it by setting `record: true` in session config");if(!this.testInfo)throw new Error("session.record() requires using `session` from the test() arguments");return new se({session:this,testInfo:this.testInfo}).record()}async waitForEvent(e,t){return m(this.socket,e,t)}async waitForTimeout(e){return I(e)}async waitForElement(e,t){const n=await this.findElements(e,t);if(typeof(t==null?void 0:t.matches)=="number"?n.length===t.matches:n.length>0)return n;throw new Error(`Element not found: | ||
${JSON.stringify(e)}`)}}class re extends $.EventEmitter{constructor({socket:e}){super(),this.socket=e,this.socket.on("*",({type:t,value:n})=>{const i=U(t,n);if(i)this.emit(i.type,i.value),this.emit("*",i);else switch(t){case"newSession":break;default:this.emit(t,n),this.emit("*",{type:t,value:n})}}),this.socket.on("disconnect",()=>{throw new Error("Client disconnected unexpectedly")})}on(e,t){return super.on(e,t)}async startSession(e){throw new Error("Not implemented")}async config(e){throw new Error("Not implemented")}async waitForSessionStart(e){return new Promise(async(t,n)=>{const i=()=>{n(new Error("Session failed to start - client disconnected"))},o=c=>{n(new Error(`Session failed to start - ${typeof c.message=="object"?JSON.stringify(c.message):c.message}`))},r=c=>{c.message.match(/Too many requests/)&&n(new Error("Session failed to start - too many requests"))};try{this.on("error",r),e.on("disconnect",i),e.on("error",o),await e.waitForEvent("ready",{timeout:null})}finally{this.off("error",r),e.off("disconnect",i),e.off("error",o)}t(e)})}}const q=new WeakMap;class ae extends re{constructor({page:e}){var o;const t=(o=q.get(e))!=null?o:new ee({page:e});q.set(e,t),t.init();const n=new N({type:"webserver",page:e,window:t});super({socket:n}),this.page=e,this.window=t,this.window.on("*",({type:r,value:c})=>{switch(r){case"config":this.currentConfig=c;break}});let i=!1;this.on("queue",({type:r,position:c})=>{r==="account"&&i!=="account"?(i="account",g("All slots for this account are currently in use. Please wait until a slot becomes available.")):(i==="account"?g("You are now in the regular queue."):i||g("All devices are currently in use. Please wait until requested device becomes available."),i="session"),c>0&&g(`Position in queue: ${c}`)}),this.on("session",()=>{i&&(g("Session is ready to start"),i=!1)})}async startSession({testInfo:e,...t}={}){var r;await this.window.waitUntilReady();const n=(r=this.page.context().browser())==null?void 0:r.browserType().name(),i=await this.config({codec:n==="chromium"?"jpeg":"h264",record:!0,...t,apiVersion:O}),o=new oe({config:i,page:this.page,window:this.window,testInfo:e});return this.window.postMessage({type:"requestSession"}),await this.waitForSessionStart(o),o}async config({publicKey:e,...t}){return e&&await Promise.all([this.window.postMessage({type:"loadApp",value:e}),m(this.window,"app",{predicate(i){return i.publicKey===e}})]),this.window.postMessage({type:"setConfig",value:t}),await m(this.window,"config")}}k.expect.extend({toHaveElement:async(s,e,t={})=>{try{const n=await s.findElements(e,t);return{pass:typeof t.matches=="number"?n.length===t.matches:n.length>0,message:()=>`Element not found: | ||
${JSON.stringify(e)}`}}catch(n){return{pass:!1,message:()=>n.message}}}});let E,S,T;const A=Object.assign(k.test.extend({_autoSnapshotSuffix:[async({},s,e)=>{e.snapshotSuffix="",await s()},{auto:!0}],page:async({},s)=>{if(!S)throw new Error("Appetize not initialized. Make sure you have run test.setup()");await s(S.page)},session:async({},s)=>{if(!T)throw new Error("Session was not started. Make sure you have run test.setup()");await s(T)},client:async({},s)=>{if(!S)throw new Error("Appetize not initialized. Make sure you have run test.setup()");await s(S)}}),{setup(s){A.afterEach(async({session:e},t)=>{}),A.beforeAll(async({browser:e,baseURL:t},n)=>{var i,o;if(n.config.fullyParallel)throw new Error("fullyParallel is not allowed when running Appetize tests. Please set `fullyParallel: false` in your Playwright config");if(A.setTimeout(6e4*5),E||(E=await e.newPage(),E.on("close",()=>{E=null,S=null,T=null})),"url"in s)await E.goto(s.url);else{const r=new URLSearchParams;s.device&&r.set("device",s.device),s.deviceColor&&r.set("deviceColor",s.deviceColor),s.screenOnly&&r.set("screenOnly",s.screenOnly.toString()),r.set("scale",(o=(i=s.scale)==null?void 0:i.toString())!=null?o:"auto"),await E.goto(`${t!=null?t:"https://appetize.io"}/embed/${s.publicKey}?${r.toString()}`)}S=new ae({page:E}),T=await W(()=>S.startSession({...s,testInfo:n}),{retries:5,timeout:3e4,predicate:(r,c)=>r instanceof Error&&r.message.match(/too many requests/)?(console.warn(`Too many session requests. Retrying in 30 seconds... (attempt #${c})`),!0):!1})}),A.afterAll(async({session:e},t)=>{await e.page.close()})}});Object.defineProperty(d,"expect",{enumerable:!0,get:function(){return k.expect}}),d.SwipeGesture=C,d.getSwipeUnit=y,d.getSwipeValue=h,d.test=A,Object.defineProperties(d,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}); | ||
//# sourceMappingURL=index.umd.js.map |
@@ -1,28 +0,6 @@ | ||
import { expect, Page, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, TestType } from '@playwright/test'; | ||
import { SessionConfig } from '../../core/session'; | ||
import { PlaywrightClient } from './client'; | ||
import './expect'; | ||
import { PlaywrightSession } from './session'; | ||
export * from '../../core/SwipeGesture'; | ||
export * from '../../core/actions'; | ||
export { expect }; | ||
export type { PlaywrightClient, PlaywrightSession }; | ||
interface TestArgs extends PlaywrightTestArgs, PlaywrightTestOptions { | ||
session: PlaywrightSession; | ||
client: PlaywrightClient; | ||
} | ||
interface WorkerArgs extends PlaywrightWorkerArgs, PlaywrightWorkerOptions { | ||
session: PlaywrightSession; | ||
client: PlaywrightClient; | ||
} | ||
type SetupOptions = ({ | ||
publicKey: string; | ||
} | { | ||
url: string; | ||
}) & { | ||
selector?: string; | ||
} & Partial<SessionConfig>; | ||
export declare const test: TestType<TestArgs, WorkerArgs> & { | ||
setup: (this: TestType<any, any>, options: SetupOptions) => void; | ||
}; | ||
export declare function getClient(page: Page): PlaywrightClient; | ||
export { expect } from '@playwright/test'; | ||
export { test } from './fixture'; | ||
export type { PlaywrightClient } from './client'; | ||
export type { PlaywrightSession } from './session'; |
/// <reference types="node" /> | ||
import { Page, TestInfo } from '@playwright/test'; | ||
import { Session, SessionConfig } from '../../core/session'; | ||
import { PlayActionOptions, Session, SessionConfig, SessionEvents } from '../../core/session'; | ||
import { WaitForEventOptionsOrPredicate } from '../../core/waitFor'; | ||
import type { Element } from '../../core/actions'; | ||
import { AppetizeWindow } from './socket'; | ||
@@ -27,2 +29,9 @@ export declare class PlaywrightSession extends Session { | ||
record(): Promise<void>; | ||
waitForEvent<K extends keyof SessionEvents>(event: K, options?: WaitForEventOptionsOrPredicate<SessionEvents[K]>): Promise<SessionEvents[K]>; | ||
waitForTimeout(ms: number): Promise<unknown>; | ||
waitForElement(element: Element, options?: WaitForElementOptions): Promise<Element[]>; | ||
} | ||
interface WaitForElementOptions extends PlayActionOptions { | ||
matches?: number; | ||
} | ||
export {}; |
@@ -5,3 +5,3 @@ /// <reference types="node" /> | ||
import { SocketProtocol } from '../../core/socket'; | ||
import { WaitForEventOptions } from '../../core/util'; | ||
import { WaitForEventOptions } from '../../core/waitFor'; | ||
export declare class AppetizeWindow extends EventEmitter { | ||
@@ -8,0 +8,0 @@ page: Page; |
{ | ||
"name": "@appetize/playwright", | ||
"version": "0.1.0", | ||
"version": "0.1.1", | ||
"description": "Test your mobile apps on Appetize.io with Playwright", | ||
"files": [ | ||
"dist" | ||
"dist", | ||
"README.md" | ||
], | ||
@@ -8,0 +9,0 @@ "keywords": [ |
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
No README
QualityPackage does not have a README. This may indicate a failed publish or a low quality package.
Found 1 instance in 1 package
337542
26
2405
0
6