Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@appetize/playwright

Package Overview
Dependencies
Maintainers
4
Versions
51
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@appetize/playwright - npm Package Compare versions

Comparing version 0.1.4 to 0.1.5

dist/core/client/headful.d.ts

5

dist/core/util.d.ts

@@ -10,1 +10,6 @@ export declare function retry<T>(fn: () => T | Promise<T>, { retries, timeout, predicate, }: {

} | undefined;
export declare function omitUndefinedNull<T>(obj: T): T;
type UnionKeys<T> = T extends T ? keyof T : never;
type StrictUnionHelper<T, TAll> = T extends any ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;
export type StrictUnion<T> = StrictUnionHelper<T, T>;
export {};

854

dist/index.es.js

@@ -5,126 +5,6 @@ import { expect, test as test$1 } from "@playwright/test";

import fs from "fs";
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 log = function() {
const context = "[Appetize]";
return Function.prototype.bind.call(console.log, console, context);
}();
async function retry(fn, {

@@ -249,2 +129,179 @@ retries = 3,

}
function omitUndefinedNull(obj) {
if (typeof obj === "object" && obj !== null) {
return Object.entries(obj).reduce((acc, [key, value]) => {
const v = omitUndefinedNull(value);
if (v !== void 0 && v !== null) {
acc[key] = v;
}
return acc;
}, {});
}
return obj;
}
class Client extends EventEmitter {
constructor({ socket }) {
super();
this.socket = socket;
this.socket.on("*", ({ type, value }) => {
const converted = convertLegacyEvent(type, value);
if (converted) {
this.emit(converted.type, converted.value);
this.emit("*", converted);
} else {
switch (type) {
case "newSession":
break;
default:
this.emit(type, value);
this.emit("*", { type, value });
}
}
});
this.socket.on("disconnect", () => {
throw new Error("Client disconnected unexpectedly");
});
}
on(event, listener) {
return super.on(event, listener);
}
async startSession(config) {
throw new Error("Not implemented");
}
async config(config) {
throw new Error("Not implemented");
}
async waitForSessionStart(session2) {
return new Promise(async (resolve, reject) => {
const handleDisconnect = () => {
reject(
new Error("Session failed to start - client disconnected")
);
};
const handleSessionError = (ev) => {
reject(
new Error(
`Session failed to start - ${typeof ev.message === "object" ? JSON.stringify(ev.message) : ev.message}`
)
);
};
const handleClientError = (ev) => {
if (ev.message.match(/Too many requests/)) {
reject(
new Error(`Session failed to start - too many requests`)
);
}
};
try {
this.on("error", handleClientError);
session2.on("disconnect", handleDisconnect);
session2.on("error", handleSessionError);
await session2.waitForEvent("ready", { timeout: null });
} finally {
this.off("error", handleClientError);
session2.off("disconnect", handleDisconnect);
session2.off("error", handleSessionError);
}
resolve(session2);
});
}
}
const version = "0.1.6";
const VERSION = version;
class HeadfulClient extends Client {
constructor({
socket,
window: window2
}) {
super({ socket });
this.window = window2;
this.window.on("*", async ({ type, value }) => {
switch (type) {
case "app":
this.app = value;
this.emit(type, value);
break;
case "deviceInfo":
this.deviceInfo = value;
this.emit(type, value);
break;
case "config":
this._config = this.mapConfig(value);
break;
case "sessionConnecting": {
if (!this.session) {
this.session = await this.createSession(this._config);
}
try {
await this.waitForSessionStart(this.session);
this.emit("session", this.session);
} catch (e) {
}
}
}
});
this.window.waitUntilReady().then(() => {
this.config({
record: true,
apiVersion: VERSION
});
});
this.getDeviceInfo();
}
async startSession(config, opts) {
if (!this.deviceInfo) {
await this.getDeviceInfo();
}
const validatedConfig = await this.config(config != null ? config : {});
const session2 = await this.createSession(validatedConfig, opts);
await Promise.all([
this.window.postMessage({ type: "requestSession" }, true),
this.waitForSessionStart(session2)
]);
return session2;
}
async config({
publicKey,
...config
}) {
if (publicKey) {
const response = await this.window.postMessage(
{
type: "loadApp",
value: publicKey
},
true
);
if (response && "error" in response) {
throw new Error(response.error);
}
}
const value = await this.window.postMessage(
{
type: "setConfig",
value: this.validateConfig(config != null ? config : {})
},
true
).then(this.mapConfig);
this._config = value;
return value;
}
mapConfig(config) {
return {
...config,
device: config.deviceType || config.device
};
}
getDeviceInfo() {
return this.window.postMessage({ type: "getDeviceInfo" }, true).then((deviceInfo) => {
this.deviceInfo = deviceInfo;
});
}
validateConfig(config) {
return config;
}
async createSession(config, opts) {
throw new Error("Not implemented");
}
}
async function waitFor(fn, timeout = 5e3) {

@@ -311,2 +368,67 @@ const start = Date.now();

}
class PlayActionError extends Error {
constructor(playbackError) {
var _a, _b, _c, _d;
let message = playbackError.message;
const event = (_a = playbackError.playback) == null ? void 0 : _a.event;
switch (playbackError.errorId) {
case "unknown":
message = `${playbackError.message}`;
break;
case "notFound":
if (event && "element" in event) {
let isMultiple = false;
const element = typeof event.element === "object" ? { ...event.element } : event.element;
if (typeof element === "object" && "allowMultipleMatches" in element) {
isMultiple = !!element.allowMultipleMatches;
delete element.allowMultipleMatches;
}
message = `No element${isMultiple ? "s" : ""} found for selector
${JSON.stringify(
element,
null,
" "
)}`;
}
break;
case "ambiguousMatch": {
const cleanedMatches = (_b = playbackError.matchedElements) == null ? void 0 : _b.map(
({
frame,
address,
frameInWindow,
bounds,
windowType,
...m
}) => m
);
if (cleanedMatches) {
message = `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
${cleanedMatches.map(
(m, index) => `${index}: ${JSON.stringify(m, null, " ")}`
).join(",\n")}`;
}
break;
}
default: {
const playback = playbackError.playback;
if (playback == null ? void 0 : playback.event.id) {
message = `Action (id: "${playback == null ? void 0 : playback.event.id}") failed: ${(_c = playbackError.message) != null ? _c : playbackError.errorId}`;
} else {
message = `Action (type: "${playback == null ? void 0 : playback.event.type}") failed: ${(_d = playbackError.message) != null ? _d : playbackError.errorId}`;
}
}
}
super(message);
this.name = "PlayActionError";
}
}
class RecorderRequiredError extends Error {
constructor(feature) {
super(
`App Recorder must be enabled to use ${feature}. Please set "record" to true in the config.`
);
this.name = "RecorderRequiredError";
}
}
const digitCharsWithShift = [

@@ -406,71 +528,162 @@ ")",

}
class PlayActionError extends Error {
constructor(playbackError) {
var _a, _b, _c, _d;
let message = playbackError.message;
const event = (_a = playbackError.playback) == null ? void 0 : _a.event;
switch (playbackError.errorId) {
case "unknown":
message = `${playbackError.message}`;
break;
case "notFound":
if (event && "element" in event) {
let isMultiple = false;
const element = typeof event.element === "object" ? { ...event.element } : event.element;
if (typeof element === "object" && "allowMultipleMatches" in element) {
isMultiple = !!element.allowMultipleMatches;
delete element.allowMultipleMatches;
class SwipeGesture {
constructor(session2, { duration = 500, ...args }) {
var _a, _b;
this.path = [];
this.easing = (t) => t;
this.session = session2;
this.element = args.element;
if (args == null ? void 0 : args.easing) {
if (typeof args.easing === "string") {
this.easing = Easings[args.easing];
} else {
this.easing = args.easing;
}
}
this.steps = Math.floor(duration / 16);
this.stepDuration = duration / this.steps;
this.move((_a = args == null ? void 0 : args.x) != null ? _a : 0, (_b = args == null ? void 0 : args.y) != null ? _b : 0);
}
move(x, y) {
var _a;
const previous = (_a = this.previousCommand(this.path, "move")) != null ? _a : {
x: 0,
y: 0
};
const px = this.parseValue(previous.x, "x");
const py = this.parseValue(previous.y, "y");
const nx = this.parseValue(x, "x");
const ny = this.parseValue(y, "y");
this.path.push({
type: "move",
x: px + nx,
y: py + ny
});
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: omitUndefinedNull(this.element)
};
}
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 endX = this.parseValue(end.x, "x");
const endY = this.parseValue(end.y, "y");
const numSteps = Math.floor(
this.steps / this.path.length - 1
);
if (numSteps < 1) {
interpolatedPath.push({
x: endX,
y: endY
});
} else {
const stepX = (endX - this.parseValue(start.x, "x")) / numSteps;
const stepY = (endY - this.parseValue(start.y, "y")) / numSteps;
for (let j = 0; j <= numSteps; j++) {
const x = this.parseValue(start.x, "x") + stepX * j;
const y = this.parseValue(start.y, "y") + stepY * j;
interpolatedPath.push({
x,
y
});
}
}
message = `No element${isMultiple ? "s" : ""} found for selector
${JSON.stringify(
element,
null,
" "
)}`;
} 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
});
}
}
break;
case "ambiguousMatch": {
const cleanedMatches = (_b = playbackError.matchedElements) == null ? void 0 : _b.map(
({
frame,
address,
frameInWindow,
bounds,
windowType,
...m
}) => m
);
if (cleanedMatches) {
message = `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
${cleanedMatches.map(
(m, index) => `${index}: ${JSON.stringify(m, null, " ")}`
).join(",\n")}`;
}
break;
}
default: {
const playback = playbackError.playback;
if (playback == null ? void 0 : playback.event.id) {
message = `Action (id: "${playback == null ? void 0 : playback.event.id}") failed: ${(_c = playbackError.message) != null ? _c : playbackError.errorId}`;
} else {
message = `Action (type: "${playback == null ? void 0 : playback.event.type}") failed: ${(_d = playbackError.message) != null ? _d : playbackError.errorId}`;
}
}
}
super(message);
this.name = "PlayActionError";
if (interpolatedPath.length) {
interpolatedPath.unshift(interpolatedPath[0]);
interpolatedPath.push(
interpolatedPath[interpolatedPath.length - 1]
);
}
return interpolatedPath;
}
}
class RecorderRequiredError extends Error {
constructor(feature) {
super(
`App Recorder must be enabled to use ${feature}. Please set "record" to true in the config.`
previousCommand(path, type) {
return path.slice().reverse().find((p) => p.type === type);
}
parseValue(value, plane) {
var _a;
const scaleFactor = (_a = this.session.deviceInfo.screen.devicePixelRatio) != null ? _a : 1;
const screenWidth = this.session.deviceInfo.screen.width * scaleFactor;
const screenHeight = this.session.deviceInfo.screen.height * scaleFactor;
if (typeof value === "number") {
return value * scaleFactor;
}
if (value.endsWith("px")) {
return parseInt(value) * scaleFactor;
}
if (value.endsWith("%")) {
const percent = parseInt(value) / 100;
const bounds = this.element ? this.getElementBounds(this.element) : {
width: screenWidth,
height: screenHeight
};
return percent * (plane === "x" ? bounds.width : bounds.height);
}
if (parseInt(value) === 0) {
return 0;
}
throw new Error(
`Invalid value: ${value}. Must be a number or string that ends with 'px' or '%'.`
);
this.name = "RecorderRequiredError";
}
getElementBounds(el) {
if (el.bounds) {
return {
x: el.bounds.left,
y: el.bounds.top,
width: el.bounds.right - el.bounds.left,
height: el.bounds.bottom - el.bounds.top
};
}
return {
x: el.frameInWindow.x,
y: el.frameInWindow.y,
width: el.frameInWindow.width,
height: el.frameInWindow.height
};
}
}
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
};
class Session extends EventEmitter {
constructor({
socket,
config
config,
deviceInfo
}) {

@@ -516,2 +729,3 @@ super();

this.socket = socket;
this.deviceInfo = deviceInfo;
const handleSocketEvent = ({ type, value }) => {

@@ -815,3 +1029,3 @@ const converted = convertLegacyEvent(type, value);

return result.matchedElements[0];
} else if (result.matchedElement) {
} else {
return result.matchedElement;

@@ -868,2 +1082,3 @@ }

async swipe(args) {
var _a, _b;
if (!this.config.record) {

@@ -873,76 +1088,46 @@ throw new RecorderRequiredError("swipe()");

let action;
if (args instanceof SwipeGesture) {
action = args.toAction();
const element = args.element ? await this.findElement(args.element) : void 0;
if (typeof args.gesture === "function") {
const gesture = new SwipeGesture(this, {
...args,
element
});
args.gesture(gesture);
action = gesture.toAction();
} else {
const { duration = 300, direction } = args;
let distance = args.distance;
const gesture = new SwipeGesture({
steps: 25,
stepDuration: duration / 25
const gesture = new SwipeGesture(this, {
...args,
element
});
if ("element" in args) {
if (!distance) {
distance = direction === "up" || direction === "down" ? "50vh" : "50vw";
const screenWidth = this.deviceInfo.screen.width * ((_a = this.deviceInfo.screen.devicePixelRatio) != null ? _a : 1);
const screenHeight = this.deviceInfo.screen.height * ((_b = this.deviceInfo.screen.devicePixelRatio) != null ? _b : 1);
switch (args.gesture) {
case "up":
case "down": {
const distance = screenHeight * 0.5;
if (typeof distance === "number") {
gesture.move(
0,
distance * (args.gesture === "up" ? -1 : 1)
);
} else {
const sign = args.gesture === "up" ? "-" : "";
gesture.move(0, sign + distance);
}
break;
}
gesture.from(args.element);
const value = getSwipeValue(distance);
const unit = getSwipeUnit(distance);
const sign = direction === "up" || direction === "left" ? -1 : 1;
switch (direction) {
case "up":
case "down":
gesture.to(0, sign * value + unit);
break;
case "left":
case "right":
gesture.to(sign * value + unit, 0);
break;
}
} else {
const xValue = getSwipeValue(args.x);
const xUnit = getSwipeUnit(args.x);
const yValue = getSwipeValue(args.y);
const yUnit = getSwipeUnit(args.y);
if (!distance) {
if (direction === "left" || direction === "right") {
if (xUnit) {
distance = 50 + xUnit;
} else {
distance = 300;
}
case "left":
case "right": {
const distance = screenWidth * 0.5;
if (typeof distance === "number") {
gesture.move(
distance * (args.gesture === "left" ? -1 : 1),
0
);
} else {
if (yUnit) {
distance = 50 + yUnit;
} else {
distance = 300;
}
const sign = args.gesture === "left" ? "-" : "";
gesture.move(sign + distance, 0);
}
break;
}
const value = getSwipeValue(distance);
const unit = getSwipeUnit(distance);
if ((direction === "up" || direction === "down") && yUnit !== unit) {
throw new Error(
`Distance unit (${unit || "px"}) does not match y unit (${yUnit || "px"})`
);
}
if ((direction === "left" || direction === "right") && xUnit !== unit) {
throw new Error(
`Distance unit (${unit || "px"}) does not match x unit (${xUnit || "px"})`
);
}
gesture.from(args.x, args.y);
switch (direction) {
case "up":
gesture.to(args.x, `${yValue - value}${yUnit}`);
break;
case "down":
gesture.to(args.x, `${yValue + value}${yUnit}`);
break;
case "left":
gesture.to(`${xValue - value}${xUnit}`, args.y);
break;
case "right":
gesture.to(`${xValue + value}${xUnit}`, args.y);
break;
}
}

@@ -993,4 +1178,2 @@ action = gesture.toAction();

}
const version = "0.1.5";
const VERSION = version;
class AppetizeWindow extends EventEmitter {

@@ -1001,6 +1184,6 @@ constructor({ page: page2 }) {

this.page = page2;
this.page.exposeFunction("__appetize_on", (ev) => {
const type = ev === "string" ? ev : ev.type;
this.emit(type, ev.value);
this.emit("*", { type, value: ev.value });
this.page.exposeFunction("__appetize_on", (data) => {
const type = data === "string" ? data : data.type;
this.emit(type, data.value);
this.emit("*", { type, value: data.value });
});

@@ -1144,6 +1327,2 @@ }

}
const log = function() {
const context = "[Appetize]";
return Function.prototype.bind.call(console.log, console, context);
}();
class Codegen {

@@ -1302,3 +1481,4 @@ constructor({

window: window2,
testInfo
testInfo,
deviceInfo
}) {

@@ -1310,3 +1490,3 @@ const socket = new PlaywrightSocket({

});
super({ socket, config });
super({ socket, config, deviceInfo });
this.window = window2;

@@ -1397,71 +1577,4 @@ this.page = page2;

}
class Client extends EventEmitter {
constructor({ socket }) {
super();
this.socket = socket;
this.socket.on("*", ({ type, value }) => {
const converted = convertLegacyEvent(type, value);
if (converted) {
this.emit(converted.type, converted.value);
this.emit("*", converted);
} else {
switch (type) {
case "newSession":
break;
default:
this.emit(type, value);
this.emit("*", { type, value });
}
}
});
this.socket.on("disconnect", () => {
throw new Error("Client disconnected unexpectedly");
});
}
on(event, listener) {
return super.on(event, listener);
}
async startSession(config) {
throw new Error("Not implemented");
}
async config(config) {
throw new Error("Not implemented");
}
async waitForSessionStart(session2) {
return new Promise(async (resolve, reject) => {
const handleDisconnect = () => {
reject(
new Error("Session failed to start - client disconnected")
);
};
const handleSessionError = (ev) => {
reject(
new Error(
`Session failed to start - ${typeof ev.message === "object" ? JSON.stringify(ev.message) : ev.message}`
)
);
};
const handleClientError = (ev) => {
if (ev.message.match(/Too many requests/)) {
reject(
new Error(`Session failed to start - too many requests`)
);
}
};
try {
this.on("error", handleClientError);
session2.on("disconnect", handleDisconnect);
session2.on("error", handleSessionError);
await session2.waitForEvent("ready", { timeout: null });
} finally {
this.off("error", handleClientError);
session2.off("disconnect", handleDisconnect);
session2.off("error", handleSessionError);
}
resolve(session2);
});
}
}
const windows = /* @__PURE__ */ new WeakMap();
class PlaywrightClient extends Client {
class PlaywrightClient extends HeadfulClient {
constructor({ page: page2 }) {

@@ -1477,15 +1590,5 @@ var _a;

});
super({ socket });
super({ socket, window: window2 });
this.window = window2;
this.page = page2;
this.window = window2;
this.window.on("*", ({ type, value }) => {
switch (type) {
case "config":
this.currentConfig = value;
break;
case "app":
this.emit(type, value);
break;
}
});
let isInQueue = false;

@@ -1520,47 +1623,27 @@ this.on("queue", ({ type, position }) => {

}
async startSession({
testInfo,
...config
} = {}) {
validateConfig(config) {
var _a;
await this.window.waitUntilReady();
const browserType = (_a = this.page.context().browser()) == null ? void 0 : _a.browserType().name();
const validatedConfig = await this.config({
return {
codec: browserType === "chromium" ? "jpeg" : "h264",
record: true,
...config,
apiVersion: VERSION
});
const session2 = new PlaywrightSession({
config: validatedConfig,
page: this.page,
window: this.window,
testInfo
});
this.window.postMessage({ type: "requestSession" });
await this.waitForSessionStart(session2);
return session2;
apiVersion: VERSION,
...config
};
}
async config({
publicKey,
async startSession({
testInfo,
...config
}) {
if (publicKey) {
const response = await this.window.postMessage(
{
type: "loadApp",
value: publicKey
},
true
);
if ("error" in response) {
throw new Error(response.error);
}
}
this.window.postMessage({
type: "setConfig",
value: config
return super.startSession(config, { testInfo });
}
async createSession(config, opts) {
this.session = new PlaywrightSession({
config,
page: this.page,
window: this.window,
testInfo: opts.testInfo,
deviceInfo: this.deviceInfo
});
const value = await waitForEvent(this.window, "config");
return value;
return this.session;
}

@@ -1685,7 +1768,6 @@ }

test.afterAll(async ({}, testInfo) => {
var _a;
await ((_a = session == null ? void 0 : session.page) == null ? void 0 : _a.close());
page = null;
client = null;
session = null;
if (session == null ? void 0 : session.page) {
const context = session.page.context();
await context.close();
}
});

@@ -1696,3 +1778,3 @@ }

const test = _test;
export { SwipeGesture, getSwipeUnit, getSwipeValue, test };
export { test };
//# sourceMappingURL=index.es.js.map

@@ -1,5 +0,5 @@

(function(d,b){typeof exports=="object"&&typeof module!="undefined"?b(exports,require("@playwright/test"),require("events"),require("fs")):typeof define=="function"&&define.amd?define(["exports","@playwright/test","events","fs"],b):(d=typeof globalThis!="undefined"?globalThis:d||self,b(d.playwright={},d.test$1,d.events,d.fs))})(this,function(d,b,I,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"),r=this.path[t+1];if(i){if(r.type==="move"){const o=y(r.x)||y(i.x),c=y(r.y)||y(i.y),a=Math.floor(this.steps/(this.path.length-1)),u=(h(r.x)-h(i.x))/a,l=(h(r.y)-h(i.y))/a,p=y(r.x)===y(i.x)||h(r.x)===0||h(i.x)===0,f=y(r.y)===y(i.y)||h(r.y)===0||h(i.y)===0;for(let w=0;w<=a;w++)e.push({x:p?`${h(i.x)+u*w}${o}`:i.x,y:f?`${h(i.y)+l*w}${c}`:i.y})}else if(r.type==="wait"){const o=Math.floor(r.value/this.stepDuration);for(let c=0;c<=o;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(r){if(i===e||!n(r,i))throw r;await new Promise(o=>setTimeout(o,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 M(s){return new Promise(e=>setTimeout(e,s))}async function m(s,e,t){const n=typeof t=="function"?{}:t,i=typeof t=="function"?t:t==null?void 0:t.predicate,r=typeof(n==null?void 0:n.timeout)!="undefined"?n.timeout:1e4;return new Promise((o,c)=>{const a=u=>{(!i||i(u))&&(s.off(e,a),o(u))};s.on(e,a),r!==null&&setTimeout(()=>{s.off(e,a),c(new Error(`Timed out after ${r}ms waiting for "${e}" event`))},r)})}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:'"'},J={191:"?",188:"<",189:"_",190:">",219:"{",220:"|",221:"}",192:"~",186:":",187:"+",222:'"'};function Y(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 P(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]:J[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,r,o,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=(r=e.matchedElements)==null?void 0:r.map(({frame:u,address:l,frameInWindow:p,bounds:f,windowType:w,...$})=>$);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: ${(o=e.message)!=null?o: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 T 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((r,{action:o})=>i?`let actions = ${JSON.stringify(this.actionHistory.map(c=>c.action),null,2)}
(function(p,m){typeof exports=="object"&&typeof module!="undefined"?m(exports,require("@playwright/test"),require("events"),require("fs")):typeof define=="function"&&define.amd?define(["exports","@playwright/test","events","fs"],m):(p=typeof globalThis!="undefined"?globalThis:p||self,m(p.playwright={},p.test$1,p.events,p.fs))})(this,function(p,m,I,q){"use strict";function W(s){return s&&typeof s=="object"&&"default"in s?s:{default:s}}var R=W(q);const f=function(){const s="[Appetize]";return Function.prototype.bind.call(console.log,console,s)}();async function L(s,{retries:e=3,timeout:t=1e3,predicate:n=()=>!0}){for(let i=1;i<=e;i++)try{return await s()}catch(r){if(i===e||!n(r,i))throw r;await new Promise(o=>setTimeout(o,t))}throw null}function $(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 D(s){return typeof s=="object"&&s!==null?Object.entries(s).reduce((e,[t,n])=>{const i=D(n);return i!=null&&(e[t]=i),e},{}):s}class z extends I.EventEmitter{constructor({socket:e}){super(),this.socket=e,this.socket.on("*",({type:t,value:n})=>{const i=$(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"))},r=c=>{n(new Error(`Session failed to start - ${typeof c.message=="object"?JSON.stringify(c.message):c.message}`))},o=c=>{c.message.match(/Too many requests/)&&n(new Error("Session failed to start - too many requests"))};try{this.on("error",o),e.on("disconnect",i),e.on("error",r),await e.waitForEvent("ready",{timeout:null})}finally{this.off("error",o),e.off("disconnect",i),e.off("error",r)}t(e)})}}const P="0.1.6";class V extends z{constructor({socket:e,window:t}){super({socket:e}),this.window=t,this.window.on("*",async({type:n,value:i})=>{switch(n){case"app":this.app=i,this.emit(n,i);break;case"deviceInfo":this.deviceInfo=i,this.emit(n,i);break;case"config":this._config=this.mapConfig(i);break;case"sessionConnecting":{this.session||(this.session=await this.createSession(this._config));try{await this.waitForSessionStart(this.session),this.emit("session",this.session)}catch{}}}}),this.window.waitUntilReady().then(()=>{this.config({record:!0,apiVersion:P})}),this.getDeviceInfo()}async startSession(e,t){this.deviceInfo||await this.getDeviceInfo();const n=await this.config(e!=null?e:{}),i=await this.createSession(n,t);return await Promise.all([this.window.postMessage({type:"requestSession"},!0),this.waitForSessionStart(i)]),i}async config({publicKey:e,...t}){if(e){const i=await this.window.postMessage({type:"loadApp",value:e},!0);if(i&&"error"in i)throw new Error(i.error)}const n=await this.window.postMessage({type:"setConfig",value:this.validateConfig(t!=null?t:{})},!0).then(this.mapConfig);return this._config=n,n}mapConfig(e){return{...e,device:e.deviceType||e.device}}getDeviceInfo(){return this.window.postMessage({type:"getDeviceInfo"},!0).then(e=>{this.deviceInfo=e})}validateConfig(e){return e}async createSession(e,t){throw new Error("Not implemented")}}async function T(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 x(s){return new Promise(e=>setTimeout(e,s))}async function g(s,e,t){const n=typeof t=="function"?{}:t,i=typeof t=="function"?t:t==null?void 0:t.predicate,r=typeof(n==null?void 0:n.timeout)!="undefined"?n.timeout:1e4;return new Promise((o,c)=>{const a=u=>{(!i||i(u))&&(s.off(e,a),o(u))};s.on(e,a),r!==null&&setTimeout(()=>{s.off(e,a),c(new Error(`Timed out after ${r}ms waiting for "${e}" event`))},r)})}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 j(s,e){const t=H(s),n=btoa(t);return`data:${e};base64,`+n}class J extends Error{constructor(e){var i,r,o,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=(r=e.matchedElements)==null?void 0:r.map(({frame:u,address:h,frameInWindow:l,bounds:d,windowType:E,...A})=>A);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,h)=>`${h}: ${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: ${(o=e.message)!=null?o: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"}}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 B(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 C(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 Q(s){switch(s){case"HOME":return"home";case"VOLUME_UP":return"volumeUp";case"VOLUME_DOWN":return"volumeDown"}return s}class O{constructor(e,{duration:t=500,...n}){var i,r;this.path=[],this.easing=o=>o,this.session=e,this.element=n.element,n!=null&&n.easing&&(typeof n.easing=="string"?this.easing=X[n.easing]:this.easing=n.easing),this.steps=Math.floor(t/16),this.stepDuration=t/this.steps,this.move((i=n==null?void 0:n.x)!=null?i:0,(r=n==null?void 0:n.y)!=null?r:0)}move(e,t){var a;const n=(a=this.previousCommand(this.path,"move"))!=null?a:{x:0,y:0},i=this.parseValue(n.x,"x"),r=this.parseValue(n.y,"y"),o=this.parseValue(e,"x"),c=this.parseValue(t,"y");return this.path.push({type:"move",x:i+o,y:r+c}),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:D(this.element)}}interpolatePath(){const e=[];for(let t=0;t<this.path.length-1;t++){const i=this.previousCommand(this.path.slice(0,t+1),"move"),r=this.path[t+1];if(i){if(r.type==="move"){const o=this.parseValue(r.x,"x"),c=this.parseValue(r.y,"y"),a=Math.floor(this.steps/this.path.length-1);if(a<1)e.push({x:o,y:c});else{const u=(o-this.parseValue(i.x,"x"))/a,h=(c-this.parseValue(i.y,"y"))/a;for(let l=0;l<=a;l++){const d=this.parseValue(i.x,"x")+u*l,E=this.parseValue(i.y,"y")+h*l;e.push({x:d,y:E})}}}else if(r.type==="wait"){const o=Math.floor(r.value/this.stepDuration);for(let c=0;c<=o;c++)e.push({x:i.x,y:i.y})}}}return e.length&&(e.unshift(e[0]),e.push(e[e.length-1])),e}previousCommand(e,t){return e.slice().reverse().find(n=>n.type===t)}parseValue(e,t){var o;const n=(o=this.session.deviceInfo.screen.devicePixelRatio)!=null?o:1,i=this.session.deviceInfo.screen.width*n,r=this.session.deviceInfo.screen.height*n;if(typeof e=="number")return e*n;if(e.endsWith("px"))return parseInt(e)*n;if(e.endsWith("%")){const c=parseInt(e)/100,a=this.element?this.getElementBounds(this.element):{width:i,height:r};return c*(t==="x"?a.width:a.height)}if(parseInt(e)===0)return 0;throw new Error(`Invalid value: ${e}. Must be a number or string that ends with 'px' or '%'.`)}getElementBounds(e){return e.bounds?{x:e.bounds.left,y:e.bounds.top,width:e.bounds.right-e.bounds.left,height:e.bounds.bottom-e.bounds.top}:{x:e.frameInWindow.x,y:e.frameInWindow.y,width:e.frameInWindow.width,height:e.frameInWindow.height}}}const X={linear:s=>s,easeIn:s=>s*s,easeOut:s=>s*(2-s),easeInOut:s=>s<.5?2*s*s:-1+(4-2*s)*s};class G extends I.EventEmitter{constructor({socket:e,config:t,deviceInfo:n}){super(),this.actionHistory=[],this.isEnding=!1,this.debug={printActions:({xdoc:r=!1}={})=>{console.log(this.actionHistory.reduce((o,{action:c})=>r?`let actions = ${JSON.stringify(this.actionHistory.map(a=>a.action),null,2)}

@@ -21,4 +21,4 @@ let nextAction = actions.shift()

window.postMessage({ type: 'playEvent', value: { event: nextAction } }, '*')
`:" "+r+`
`+JSON.stringify(o,null,2),""))}},this.config=t,this.socket=e;const n=({type:i,value:r})=>{const o=U(i,r);switch(i){case"adbOverTcp":{this.adbConnectionInfo={...r,command:G(r)};break}case"networkInspectorUrl":this.networkInspectorUrl=r;break}o?(this.emit(o.type,o.value),this.emit("*",o)):(this.emit(i,r),this.emit("*",{type:i,value:r}))};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
`:" "+o+`
`+JSON.stringify(c,null,2),""))}},this.config=t,this.socket=e,this.deviceInfo=n;const i=({type:r,value:o})=>{const c=$(r,o);switch(r){case"adbOverTcp":{this.adbConnectionInfo={...o,command:Z(o)};break}case"networkInspectorUrl":this.networkInspectorUrl=o;break}c?(this.emit(c.type,c.value),this.emit("*",c)):(this.emit(r,o),this.emit("*",{type:r,value:o}))};this.socket.on("*",i),this.on("disconnect",()=>{if(this.socket.off("*",i),!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

@@ -29,16 +29,16 @@ 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({ record: true })`),super.on(e,t)}async waitForEvent(e,t){return m(this,e,t)}async end(){this.isEnding=!0,await this.socket.disconnect()}async getNetworkInspectorUrl(){if(this.config.proxy!=="intercept")throw new Error(`Session must be configured to use a proxy in order to get the network inspector URL. You can do this with
startSession({ record: true })`),super.on(e,t)}async waitForEvent(e,t){return g(this,e,t)}async end(){this.isEnding=!0,await this.socket.disconnect()}async getNetworkInspectorUrl(){if(this.config.proxy!=="intercept")throw new Error(`Session must be configured to use a proxy in order to get the network inspector URL. 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({ proxy: "intercept" })`);return this.networkInspectorUrl?this.networkInspectorUrl:T(()=>{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: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"?(r=>typeof window=="undefined"?Buffer.from(r):r)(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 M(1e3);const t=[...e].map(Y);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:r=>r.type==="keypress"&&r.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 M(1e3)}async reinstallApp(){this.socket.send("reinstallApp"),await this.waitForEvent("appLaunch",{timeout:6e4})}async playAction(e,t={}){if(!this.config.record)throw new T("playAction()");return this.playActions([e],t).then(n=>n[0])}async playActions(e,t={}){const{timeout:n=3e4,strictMatch:i}=t,r=[];if(!this.config.record)throw new T("playActions()");for(const o of e){const{id:c,...a}=o;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",$),this.off("playbackError",S)},$=async A=>{this.actionHistory.push({action:o,result:A}),w(),p(A)},S=A=>{this.actionHistory.push({action:o,error:A}),w(),f(new Q(A))};this.once("playbackFoundAndSent",$),this.once("playbackError",S)}),this.socket.send("playEvent",{event:{...a,type:u},timeout:Math.round(n/1e3),strictMatch:i,id:c})]);r.push(l)}return r}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 T('"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 T("swipe()");let t;if(e instanceof C)t=e.toAction();else{const{duration:n=300,direction:i}=e;let r=e.distance;const o=new C({steps:25,stepDuration:n/25});if("element"in e){r||(r=i==="up"||i==="down"?"50vh":"50vw"),o.from(e.element);const c=h(r),a=y(r),u=i==="up"||i==="left"?-1:1;switch(i){case"up":case"down":o.to(0,u*c+a);break;case"left":case"right":o.to(u*c+a,0);break}}else{const c=h(e.x),a=y(e.x),u=h(e.y),l=y(e.y);r||(i==="left"||i==="right"?a?r=50+a:r=300:l?r=50+l:r=300);const p=h(r),f=y(r);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(o.from(e.x,e.y),i){case"up":o.to(e.x,`${u-p}${l}`);break;case"down":o.to(e.x,`${u+p}${l}`);break;case"left":o.to(`${c-p}${a}`,e.y);break;case"right":o.to(`${c+p}${a}`,e.y);break}}t=o.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.5";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 r=new MessageChannel;r.port1.onmessage=()=>{clearInterval(i),clearTimeout(n),t(!1)},window.postMessage({type:"init",appetizeClient:!0,version:e},"*",[r.port2]),window.__appetize_postMessage=async(o,c=!1)=>{const a=new MessageChannel;if(window.postMessage(o,"*",[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 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:r})=>{switch(i){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":if(this.type==="appetizer"){const o=Z({type:i,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 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((r,o)=>({line:r,num:o+1})).slice(this.testInfo.line).filter(({line:r})=>r.includes("session.record()"))[this.currentRecord].num;if(i!==void 0){g(`\u{1F534} Recording at line ${i}`);let r=[];const o=u=>{ie(u),ne(r,u),g(K(u))},c=u=>{r=r.filter(l=>l.id!==u.id)};this.session.on("action",o),this.session.on("deletedAction",c),await te(this.session.page),await M(2e3),this.session.off("action",o);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`${r.map(S=>K(S)).reduce((S,A,ce)=>`${S}
// ${ce+1}. ${A}`,"// Recorded using session.record()")}
startSession({ enableAdb: true })`);return this.adbConnectionInfo?this.adbConnectionInfo:T(()=>{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 g(this.socket,"screenshot",{timeout:6e4});if(!t.success)throw new Error("Screenshot failed");return{data:e==="buffer"?(r=>typeof window=="undefined"?Buffer.from(r):r)(t.data):j(new Uint8Array(t.data),t.mimeType),mimeType:t.mimeType}}async heartbeat(){return this.socket.send("heartbeat")}async type(e){this.config.platform==="ios"&&await x(1e3);const t=[...e].map(B);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=Q(e);const n=Date.now(),[i]=await Promise.all([this.waitForEvent("interaction",{predicate:r=>r.type==="keypress"&&r.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 x(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,r=[];if(!this.config.record)throw new M("playActions()");for(const o of e){const{id:c,...a}=o;let u=a.type;switch(u){case"tap":u="click";break}const[h]=await Promise.all([new Promise((l,d)=>{const E=()=>{this.off("playbackFoundAndSent",A),this.off("playbackError",w)},A=async b=>{this.actionHistory.push({action:o,result:b}),E(),l(b)},w=b=>{this.actionHistory.push({action:o,error:b}),E(),d(new J(b))};this.once("playbackFoundAndSent",A),this.once("playbackError",w)}),this.socket.send("playEvent",{event:{...a,type:u},timeout:Math.round(n/1e3),strictMatch:i,id:c})]);r.push(h)}return r}async getUI({timeout:e=3e4}={}){this.socket.send("dumpUi");const t=await g(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});return i.matchedElements?i.matchedElements[0]: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){var i,r;if(!this.config.record)throw new M("swipe()");let t;const n=e.element?await this.findElement(e.element):void 0;if(typeof e.gesture=="function"){const o=new O(this,{...e,element:n});e.gesture(o),t=o.toAction()}else{const o=new O(this,{...e,element:n}),c=this.deviceInfo.screen.width*((i=this.deviceInfo.screen.devicePixelRatio)!=null?i:1),a=this.deviceInfo.screen.height*((r=this.deviceInfo.screen.devicePixelRatio)!=null?r:1);switch(e.gesture){case"up":case"down":{const u=a*.5;if(typeof u=="number")o.move(0,u*(e.gesture==="up"?-1:1));else{const h=e.gesture==="up"?"-":"";o.move(0,h+u)}break}case"left":case"right":{const u=c*.5;if(typeof u=="number")o.move(u*(e.gesture==="left"?-1:1),0);else{const h=e.gesture==="left"?"-":"";o.move(h+u,0)}break}}t=o.toAction()}return this.playAction(t)}}function Z(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 ee({type:s,value:e}){switch(s){case"chromeDevToolsUrl":return{type:"networkInspectorUrl",value:e};case"orientationChanged":return{type:"orientationChanged",value:e}}}class te 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 r=new MessageChannel;r.port1.onmessage=()=>{clearInterval(i),clearTimeout(n),t(!1)},window.postMessage({type:"init",appetizeClient:!0,version:e},"*",[r.port2]),window.__appetize_postMessage=async(o,c=!1)=>{const a=new MessageChannel;if(window.postMessage(o,"*",[a.port2]),c)return new Promise((u,h)=>{const l=setTimeout(()=>{h(new Error("Timed out waiting for postMessage response"))},6e4);a.port1.onmessage=d=>{clearTimeout(l),u(d.data)}})}},100)}),[P]),await this.page.evaluate(()=>{window.addEventListener("message",e=>{e.source===window&&window.__appetize_on(e.data)})},[]),this.ready=!0}async waitUntilReady(){return T(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 U 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:r})=>{switch(i){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":if(this.type==="appetizer"){const o=ee({type:i,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 g(this,e,t)}}async function se(s){await s.pause()}class ne{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((r,o)=>({line:r,num:o+1})).slice(this.testInfo.line).filter(({line:r})=>r.includes("session.record()"))[this.currentRecord].num;if(i!==void 0){f(`\u{1F534} Recording at line ${i}`);let r=[];const o=u=>{re(u),ie(r,u),f(N(u))},c=u=>{r=r.filter(h=>h.id!==u.id)};this.session.on("action",o),this.session.on("deletedAction",c),await se(this.session.page),await x(2e3),this.session.off("action",o);const a=t.split(`
`).map((u,h)=>{var l,d;if(h===i-1){const E=(d=(l=u.match(/^\s*/))==null?void 0:l[0])!=null?d:0;return`${r.map(w=>N(w)).reduce((w,b,ce)=>`${w}
// ${ce+1}. ${b}`,"// Recorded using session.record()")}
await session.playActions(${JSON.stringify(r,null," ")})`.split(`
`).map(S=>w+S).join(`
`).map(w=>E+w).join(`
`)}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:P(t.keypress)+P(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+P(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 "${P(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 re extends X{constructor({page:e,config:t,window:n,testInfo:i}){const r=new N({page:e,window:n,type:"appetizer"});super({socket:r,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 M(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 oe extends I.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"))},r=c=>{n(new Error(`Session failed to start - ${typeof c.message=="object"?JSON.stringify(c.message):c.message}`))},o=c=>{c.message.match(/Too many requests/)&&n(new Error("Session failed to start - too many requests"))};try{this.on("error",o),e.on("disconnect",i),e.on("error",r),await e.waitForEvent("ready",{timeout:null})}finally{this.off("error",o),e.off("disconnect",i),e.off("error",r)}t(e)})}}const q=new WeakMap;class ae extends oe{constructor({page:e}){var r;const t=(r=q.get(e))!=null?r: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:o,value:c})=>{switch(o){case"config":this.currentConfig=c;break;case"app":this.emit(o,c);break}});let i=!1;this.on("queue",({type:o,position:c})=>{i||(i=!0,g(o==="account"?"All slots for this account are currently in use. Please wait until a slot becomes available.":"All devices are currently in use. Please wait until requested device becomes available.")),c>0&&g(o==="account"?`Position in account-level queue: ${c}`:`Position in queue: ${c}`)}),this.on("session",()=>{i&&(g("Session started"),i=!1)})}async startSession({testInfo:e,...t}={}){var o;await this.window.waitUntilReady();const n=(o=this.page.context().browser())==null?void 0:o.browserType().name(),i=await this.config({codec:n==="chromium"?"jpeg":"h264",record:!0,...t,apiVersion:O}),r=new re({config:i,page:this.page,window:this.window,testInfo:e});return this.window.postMessage({type:"requestSession"}),await this.waitForSessionStart(r),r}async config({publicKey:e,...t}){if(e){const i=await this.window.postMessage({type:"loadApp",value:e},!0);if("error"in i)throw new Error(i.error)}return this.window.postMessage({type:"setConfig",value:t}),await m(this.window,"config")}}b.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 v,k,E;const x=Object.assign(b.test.extend({_autoSnapshotSuffix:[async({},s,e)=>{e.snapshotSuffix="",await s()},{auto:!0}],page:async({},s)=>{if(!k)throw new Error("Appetize not initialized. Make sure you have run test.setup()");await s(k.page)},session:async({},s)=>{if(!E)throw new Error("Session was not started. Make sure you have run test.setup()");await s(E)},client:async({},s)=>{if(!k)throw new Error("Appetize not initialized. Make sure you have run test.setup()");await s(k)}}),{setup(s){x.afterEach(async({session:e},t)=>{}),x.beforeAll(async({browser:e,baseURL:t},n)=>{var i,r;if(n.config.fullyParallel)throw new Error("fullyParallel is not allowed when running Appetize tests. Please set `fullyParallel: false` in your Playwright config");if(x.setTimeout(6e4*5),v||(v=await e.newPage(),v.on("close",()=>{v=null,k=null,E=null})),"url"in s)await v.goto(s.url);else{const o=new URLSearchParams;s.device&&o.set("device",s.device),s.deviceColor&&o.set("deviceColor",s.deviceColor),s.screenOnly&&o.set("screenOnly",s.screenOnly.toString()),o.set("scale",(r=(i=s.scale)==null?void 0:i.toString())!=null?r:"auto"),await v.goto(`${t!=null?t:"https://appetize.io"}/embed/${s.publicKey}?${o.toString()}`)}k=new ae({page:v}),E=await W(()=>k.startSession({...s,testInfo:n}),{retries:5,timeout:3e4,predicate:(o,c)=>o instanceof Error&&o.message.match(/too many requests/)?(console.warn(`Too many session requests. Retrying in 30 seconds... (attempt #${c})`),!0):!1})}),x.afterAll(async({},e)=>{var t;await((t=E==null?void 0:E.page)==null?void 0:t.close()),v=null,k=null,E=null})}});Object.defineProperty(d,"expect",{enumerable:!0,get:function(){return b.expect}}),d.SwipeGesture=C,d.getSwipeUnit=y,d.getSwipeValue=h,d.test=x,Object.defineProperties(d,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
`)),f("\u{1F7E2} Finished"),this.currentRecord+=1}}}function ie(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:C(t.keypress)+C(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+C(e.keypress)}}:s.push(e);break;default:s.push(e)}else s.push(e)}function N(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 "${C(s.keypress)}"`:`type "${s.keypress.value}"`;default:return s.type}}function re(s){switch(s.type){case"click":case"swipe":delete s.ui;break}return s}class oe extends G{constructor({page:e,config:t,window:n,testInfo:i,deviceInfo:r}){const o=new U({page:e,window:n,type:"appetizer"});super({socket:o,config:t,deviceInfo:r}),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([g(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([g(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 ne({session:this,testInfo:this.testInfo}).record()}async waitForEvent(e,t){return g(this.socket,e,t)}async waitForTimeout(e){return x(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)}`)}}const K=new WeakMap;class ae extends V{constructor({page:e}){var r;const t=(r=K.get(e))!=null?r:new te({page:e});K.set(e,t),t.init();const n=new U({type:"webserver",page:e,window:t});super({socket:n,window:t}),this.window=t,this.page=e;let i=!1;this.on("queue",({type:o,position:c})=>{i||(i=!0,f(o==="account"?"All slots for this account are currently in use. Please wait until a slot becomes available.":"All devices are currently in use. Please wait until requested device becomes available.")),c>0&&f(o==="account"?`Position in account-level queue: ${c}`:`Position in queue: ${c}`)}),this.on("session",()=>{i&&(f("Session started"),i=!1)})}validateConfig(e){var n;return{codec:((n=this.page.context().browser())==null?void 0:n.browserType().name())==="chromium"?"jpeg":"h264",record:!0,apiVersion:P,...e}}async startSession({testInfo:e,...t}){return super.startSession(t,{testInfo:e})}async createSession(e,t){return this.session=new oe({config:e,page:this.page,window:this.window,testInfo:t.testInfo,deviceInfo:this.deviceInfo}),this.session}}m.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 v,k,y;const S=Object.assign(m.test.extend({_autoSnapshotSuffix:[async({},s,e)=>{e.snapshotSuffix="",await s()},{auto:!0}],page:async({},s)=>{if(!k)throw new Error("Appetize not initialized. Make sure you have run test.setup()");await s(k.page)},session:async({},s)=>{if(!y)throw new Error("Session was not started. Make sure you have run test.setup()");await s(y)},client:async({},s)=>{if(!k)throw new Error("Appetize not initialized. Make sure you have run test.setup()");await s(k)}}),{setup(s){S.afterEach(async({session:e},t)=>{}),S.beforeAll(async({browser:e,baseURL:t},n)=>{var i,r;if(n.config.fullyParallel)throw new Error("fullyParallel is not allowed when running Appetize tests. Please set `fullyParallel: false` in your Playwright config");if(S.setTimeout(6e4*5),v||(v=await e.newPage(),v.on("close",()=>{v=null,k=null,y=null})),"url"in s)await v.goto(s.url);else{const o=new URLSearchParams;s.device&&o.set("device",s.device),s.deviceColor&&o.set("deviceColor",s.deviceColor),s.screenOnly&&o.set("screenOnly",s.screenOnly.toString()),o.set("scale",(r=(i=s.scale)==null?void 0:i.toString())!=null?r:"auto"),await v.goto(`${t!=null?t:"https://appetize.io"}/embed/${s.publicKey}?${o.toString()}`)}k=new ae({page:v}),y=await L(()=>k.startSession({...s,testInfo:n}),{retries:5,timeout:3e4,predicate:(o,c)=>o instanceof Error&&o.message.match(/too many requests/)?(console.warn(`Too many session requests. Retrying in 30 seconds... (attempt #${c})`),!0):!1})}),S.afterAll(async({},e)=>{y!=null&&y.page&&await y.page.context().close()})}});Object.defineProperty(p,"expect",{enumerable:!0,get:function(){return m.expect}}),p.test=S,Object.defineProperties(p,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
//# sourceMappingURL=index.umd.js.map
import { Page, TestInfo } from '@playwright/test';
import { SessionConfig, UserSessionConfig } from '../../core/session';
import { HeadfulClient, HeadfulClientEvents } from '../../core/client/headful';
import { PlaywrightSession } from './session';
import { Client, ClientEvents } from '../../core/client';
import { SessionConfig, UserSessionConfig } from '../../core/session';
import { AppetizeWindow, PlaywrightSocket } from './socket';
export declare class PlaywrightClient extends Client<PlaywrightSocket, ClientEvents, PlaywrightSession> {
export declare class PlaywrightClient extends HeadfulClient<PlaywrightSocket, PlaywrightClientEvents, PlaywrightSession> {
page: Page;
window: AppetizeWindow;
currentConfig?: SessionConfig;
constructor({ page }: {
page: Page;
});
startSession({ testInfo, ...config }?: Partial<SessionConfig> & {
protected validateConfig(config: UserSessionConfig): {
audio?: boolean | undefined;
debug?: boolean | undefined;
device: string;
osVersion: string;
scale?: number | "auto" | undefined;
autoplay?: boolean | undefined;
adbShellCommand?: string | undefined;
androidPackageManager?: boolean | undefined;
appearance?: string | undefined;
codec: string;
deviceColor?: string | undefined;
disableSessionStart?: boolean | undefined;
disableVirtualKeyboard?: boolean | undefined;
enableAdb?: boolean | undefined;
publicKey?: string | undefined;
grantPermissions?: boolean | undefined;
hidePasswords?: boolean | undefined;
iosKeyboard?: string | undefined;
language?: string | undefined;
launchUrl?: string | undefined;
launchArgs?: (string | number)[] | undefined;
locale?: string | undefined;
location?: string[] | undefined;
loopback?: boolean | undefined;
noVideo?: boolean | undefined;
orientation?: string | undefined;
payerCode?: string | undefined;
params?: Record<string, any> | undefined;
plistEdit?: Record<string, any> | undefined;
proxy?: string | undefined;
record: boolean;
region?: string | undefined;
screenOnly?: boolean | undefined;
screenRecording?: boolean | undefined;
showRotateButtons?: boolean | undefined;
timezone?: string | undefined;
endSessionRedirectUrl?: string | undefined;
userInteractionDisabled?: boolean | undefined;
volume?: number | undefined;
debugSession?: boolean | undefined;
apiVersion: string;
};
startSession({ testInfo, ...config }: Partial<UserSessionConfig> & {
testInfo?: TestInfo;
}): Promise<PlaywrightSession>;
config({ publicKey, ...config }: Partial<UserSessionConfig>): Promise<SessionConfig>;
protected createSession(config: SessionConfig, opts: {
testInfo?: TestInfo;
}): Promise<PlaywrightSession>;
}
export interface PlaywrightClientEvents extends HeadfulClientEvents<PlaywrightSession> {
}

@@ -1,2 +0,1 @@

export * from '../../core/SwipeGesture';
export * from '../../core/actions';

@@ -3,0 +2,0 @@ export { expect } from '@playwright/test';

@@ -7,2 +7,3 @@ /// <reference types="node" />

import { AppetizeWindow } from './socket';
import { DeviceInfo } from '../../core/client';
export declare class PlaywrightSession extends Session {

@@ -13,6 +14,7 @@ page: Page;

testInfo?: TestInfo;
constructor({ page, config, window, testInfo, }: {
constructor({ page, config, window, testInfo, deviceInfo, }: {
page: Page;
config: SessionConfig;
window: AppetizeWindow;
deviceInfo: DeviceInfo;
testInfo?: TestInfo;

@@ -19,0 +21,0 @@ });

@@ -6,3 +6,4 @@ /// <reference types="node" />

import { WaitForEventOptions } from '../../core/waitFor';
export declare class AppetizeWindow extends EventEmitter {
import { AppetizeWindowProtocol } from '../../core/window';
export declare class AppetizeWindow extends EventEmitter implements AppetizeWindowProtocol {
page: Page;

@@ -9,0 +10,0 @@ ready: boolean;

{
"name": "@appetize/playwright",
"version": "0.1.4",
"version": "0.1.5",
"description": "Test your mobile apps on Appetize.io with Playwright",

@@ -35,3 +35,3 @@ "files": [

"@types/node": "^18.11.18",
"@playwright/test": "^1.29.0",
"@playwright/test": "^1.30.0",
"typescript": "^4.8.4",

@@ -41,4 +41,4 @@ "vite": "^2.9.6"

"peerDependencies": {
"@playwright/test": "^1.29.0"
"@playwright/test": "^1.30.0"
}
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc