Skip to content

Commit

Permalink
Merge pull request #292 from Zondax/dev
Browse files Browse the repository at this point in the history
  • Loading branch information
carlosala authored Aug 1, 2023
2 parents 2500a3d + 1543f7f commit 3d202d3
Show file tree
Hide file tree
Showing 5 changed files with 832 additions and 740 deletions.
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,15 @@
"copyfiles": "^2.4.1",
"eslint": "^8.43.0",
"eslint-config-prettier": "^8.7.0",
"eslint-config-standard-with-typescript": "^36.0.0",
"eslint-config-standard-with-typescript": "^37.0.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-n": "^15.0.0",
"eslint-plugin-n": "^16.0.1",
"eslint-plugin-promise": "^6.0.0",
"jest": "^29.5.0",
"prettier": "^2.8.5",
"prettier": "^3.0.0",
"rimraf": "^5.0.0",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.1",
"typescript": "5.0.4"
"typescript": "5.1.6"
}
}
115 changes: 77 additions & 38 deletions src/Zemu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export default class Zemu {
host: string = DEFAULT_HOST,
desiredTransportPort?: number,
desiredSpeculosApiPort?: number,
emuImage: string = DEFAULT_EMU_IMG
emuImage: string = DEFAULT_EMU_IMG,
) {
this.host = host;
this.desiredTransportPort = desiredTransportPort;
Expand Down Expand Up @@ -202,7 +202,7 @@ export default class Zemu {
await this.waitForText(
this.startOptions.startText,
this.startOptions.startTimeout,
this.startOptions.caseSensitive
this.startOptions.caseSensitive,
);

this.log(`Get initial snapshot and events`);
Expand Down Expand Up @@ -372,13 +372,23 @@ export default class Zemu {
this.log(`Screen changed`);
}

eventsAreEqual(events1: IEvent[], events2: IEvent[]): boolean {
if (events1.length !== events2.length) return false;
for (let i = 0; i < events1.length; i++) {
if (events1[i].x !== events2[i].x) return false;
if (events1[i].y !== events2[i].y) return false;
if (events1[i].text !== events2[i].text) return false;
}
return true;
}

async waitForScreenChanges(prevEvents: IEvent[], timeout = DEFAULT_WAIT_TIMEOUT): Promise<void> {
let currEvents = await this.getEvents();
const startTime = new Date();

this.log(`Wait for screen changes`);

while (currEvents.length === prevEvents.length) {
while (this.eventsAreEqual(prevEvents, currEvents)) {
const elapsed = new Date().getTime() - startTime.getTime();
if (elapsed > timeout) {
this.log("Timeout waiting for screen to change");
Expand Down Expand Up @@ -414,7 +424,7 @@ export default class Zemu {
waitForScreenUpdate = true,
takeSnapshots = false,
startImgIndex = 0,
timeout = DEFAULT_METHOD_TIMEOUT
timeout = DEFAULT_METHOD_TIMEOUT,
): Promise<number> {
if (this.startOptions.model !== "stax") {
const expertImgIndex = await this.toggleExpertMode(testcaseName, takeSnapshots, startImgIndex);
Expand All @@ -426,7 +436,7 @@ export default class Zemu {
takeSnapshots,
expertImgIndex,
timeout,
!nanoIsSecretMode
!nanoIsSecretMode,
);
if (nanoIsSecretMode) {
const secretClicks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // 10 double clicks
Expand All @@ -440,7 +450,7 @@ export default class Zemu {
true,
takeSnapshots,
tmpImgIndex,
timeout
timeout,
);
} else {
const nav = zondaxStaxEnableSpecialMode(staxToggleSettingButton);
Expand All @@ -454,7 +464,8 @@ export default class Zemu {
navigateSchedule: Array<INavElement | number>,
waitForScreenUpdate = true,
takeSnapshots = true,
startImgIndex = 0
startImgIndex = 0,
waitForEventsChange = false,
): Promise<number> {
const snapshotPrefixGolden = resolve(`${path}/snapshots/${testcaseName}`);
const snapshotPrefixTmp = resolve(`${path}/snapshots-tmp/${testcaseName}`);
Expand All @@ -475,7 +486,7 @@ export default class Zemu {
for (const nav of navSchedule) {
imageIndex += 1;
filename = this.getSnapshotPath(snapshotPrefixTmp, imageIndex, takeSnapshots);
await this.runAction(nav, filename, waitForScreenUpdate);
await this.runAction(nav, filename, waitForScreenUpdate, waitForEventsChange);
}

return imageIndex;
Expand All @@ -501,7 +512,7 @@ export default class Zemu {
testcaseName: string,
navigateSchedule: Array<INavElement | number>,
waitForScreenUpdate = true,
startImgIndex = 0
startImgIndex = 0,
): Promise<boolean> {
const takeSnapshots = true;
const lastImgIndex = await this.navigate(
Expand All @@ -510,7 +521,7 @@ export default class Zemu {
navigateSchedule,
waitForScreenUpdate,
takeSnapshots,
startImgIndex
startImgIndex,
);
return this.compareSnapshots(path, testcaseName, lastImgIndex);
}
Expand Down Expand Up @@ -540,7 +551,7 @@ export default class Zemu {
testcaseName: string,
waitForScreenUpdate = true,
startImgIndex = 0,
timeout = DEFAULT_METHOD_TIMEOUT
timeout = DEFAULT_METHOD_TIMEOUT,
): Promise<boolean> {
const approveKeyword = this.startOptions.approveKeyword;
const takeSnapshots = true;
Expand All @@ -551,7 +562,7 @@ export default class Zemu {
waitForScreenUpdate,
takeSnapshots,
startImgIndex,
timeout
timeout,
);
if (this.startOptions.model === "stax") {
// Avoid taking a snapshot of the final animation
Expand All @@ -566,7 +577,7 @@ export default class Zemu {
testcaseName: string,
waitForScreenUpdate = true,
startImgIndex = 0,
timeout = DEFAULT_METHOD_TIMEOUT
timeout = DEFAULT_METHOD_TIMEOUT,
): Promise<boolean> {
const rejectKeyword = this.startOptions.rejectKeyword;
if (this.startOptions.model !== "stax") {
Expand All @@ -576,7 +587,7 @@ export default class Zemu {
rejectKeyword,
waitForScreenUpdate,
startImgIndex,
timeout
timeout,
);
} else {
const takeSnapshots = true;
Expand All @@ -591,7 +602,7 @@ export default class Zemu {
takeSnapshots,
startImgIndex,
timeout,
runLastAction
runLastAction,
);
const rejectConfirmationNav = new TouchNavigation([ButtonKind.RejectButton, ButtonKind.ConfirmYesButton]);
// Overwrite last snapshot since navigate starts taking a snapshot of the current screen
Expand All @@ -601,7 +612,7 @@ export default class Zemu {
rejectConfirmationNav.schedule,
waitForScreenUpdate,
takeSnapshots,
navLastIndex - 1
navLastIndex - 1,
);
// Avoid taking a snapshot of the final animation
await this.waitUntilScreenIs(this.mainMenuSnapshot);
Expand All @@ -619,7 +630,7 @@ export default class Zemu {
startImgIndex = 0,
timeout = DEFAULT_METHOD_TIMEOUT,
runLastAction = true,
waitForInitialEventsChange = true
waitForInitialEventsChange = true,
): Promise<number> {
const snapshotPrefixGolden = resolve(`${path}/snapshots/${testcaseName}`);
const snapshotPrefixTmp = resolve(`${path}/snapshots-tmp/${testcaseName}`);
Expand Down Expand Up @@ -659,7 +670,7 @@ export default class Zemu {
type: isStaxDevice ? ActionKind.Touch : ActionKind.RightClick,
button: tapContinueButton, // For clicks, this will be ignored
};
await this.runAction(nav, filename, waitForScreenUpdate);
await this.runAction(nav, filename, waitForScreenUpdate, true);
start = new Date();
}

Expand All @@ -672,7 +683,7 @@ export default class Zemu {
type: isStaxDevice ? ActionKind.Touch : ActionKind.BothClick,
button: staxApproveButton ?? dummyButton,
};
await this.runAction(nav, filename, waitForScreenUpdate);
await this.runAction(nav, filename, waitForScreenUpdate, true);
return imageIndex;
}

Expand All @@ -683,7 +694,7 @@ export default class Zemu {
waitForScreenUpdate = true,
startImgIndex = 0,
timeout = DEFAULT_METHOD_TIMEOUT,
waitForInitialEventsChange = true
waitForInitialEventsChange = true,
): Promise<boolean> {
const takeSnapshots = true;
const lastImgIndex = await this.navigateUntilText(
Expand All @@ -695,7 +706,7 @@ export default class Zemu {
startImgIndex,
timeout,
true, // runLastAction
waitForInitialEventsChange
waitForInitialEventsChange,
);
return this.compareSnapshots(path, testcaseName, lastImgIndex);
}
Expand Down Expand Up @@ -746,7 +757,12 @@ export default class Zemu {
}
}

async click(endpoint: string, filename: string = "", waitForScreenUpdate: boolean = true): Promise<ISnapshot> {
async click(
endpoint: string,
filename: string = "",
waitForScreenUpdate: boolean = true,
waitForEventsChange: boolean = false,
): Promise<ISnapshot> {
if (!this.startOptions.model.startsWith("nano")) throw new Error("click method can only be used with nano devices");
const prevEvents = await this.getEvents();
const prevScreen = await this.snapshot();
Expand All @@ -759,7 +775,7 @@ export default class Zemu {
// Wait and poll Speculos until the application screen gets updated
if (waitForScreenUpdate) {
await this.waitUntilScreenIsNot(prevScreen);
await this.waitForScreenChanges(prevEvents);
if (waitForEventsChange) await this.waitForScreenChanges(prevEvents);
} else {
// A minimum delay is required
await Zemu.sleep();
Expand All @@ -768,19 +784,36 @@ export default class Zemu {
return await this.snapshot(filename);
}

async clickLeft(filename: string = "", waitForScreenUpdate: boolean = true): Promise<ISnapshot> {
return await this.click("/button/left", filename, waitForScreenUpdate);
async clickLeft(
filename: string = "",
waitForScreenUpdate: boolean = true,
waitForEventsChange: boolean = false,
): Promise<ISnapshot> {
return await this.click("/button/left", filename, waitForScreenUpdate, waitForEventsChange);
}

async clickRight(filename: string = "", waitForScreenUpdate: boolean = true): Promise<ISnapshot> {
return await this.click("/button/right", filename, waitForScreenUpdate);
async clickRight(
filename: string = "",
waitForScreenUpdate: boolean = true,
waitForEventsChange: boolean = false,
): Promise<ISnapshot> {
return await this.click("/button/right", filename, waitForScreenUpdate, waitForEventsChange);
}

async clickBoth(filename: string = "", waitForScreenUpdate: boolean = true): Promise<ISnapshot> {
return await this.click("/button/both", filename, waitForScreenUpdate);
async clickBoth(
filename: string = "",
waitForScreenUpdate: boolean = true,
waitForEventsChange: boolean = false,
): Promise<ISnapshot> {
return await this.click("/button/both", filename, waitForScreenUpdate, waitForEventsChange);
}

async fingerTouch(button: IButton, filename: string = "", waitForScreenUpdate: boolean = true): Promise<ISnapshot> {
async fingerTouch(
button: IButton,
filename: string = "",
waitForScreenUpdate: boolean = true,
waitForEventsChange: boolean = false,
): Promise<ISnapshot> {
if (this.startOptions.model !== "stax") throw new Error("fingerTouch method can only be used with stax device");
const prevEvents = await this.getEvents();
const prevScreen = await this.snapshot();
Expand All @@ -793,7 +826,7 @@ export default class Zemu {
// Wait and poll Speculos until the application screen gets updated
if (waitForScreenUpdate) {
await this.waitUntilScreenIsNot(prevScreen);
await this.waitForScreenChanges(prevEvents);
if (waitForEventsChange) await this.waitForScreenChanges(prevEvents);
} else {
// A minimum delay is required
await Zemu.sleep();
Expand All @@ -802,22 +835,27 @@ export default class Zemu {
return await this.snapshot(filename);
}

async runAction(navElement: INavElement, filename: string = "", waitForScreenUpdate: boolean = true): Promise<void> {
async runAction(
navElement: INavElement,
filename: string = "",
waitForScreenUpdate: boolean = true,
waitForEventsChange: boolean = false,
): Promise<void> {
switch (navElement.type) {
case ActionKind.RightClick:
await this.clickRight(filename, waitForScreenUpdate);
await this.clickRight(filename, waitForScreenUpdate, waitForEventsChange);
break;

case ActionKind.LeftClick:
await this.clickLeft(filename, waitForScreenUpdate);
await this.clickLeft(filename, waitForScreenUpdate, waitForEventsChange);
break;

case ActionKind.BothClick:
await this.clickBoth(filename, waitForScreenUpdate);
await this.clickBoth(filename, waitForScreenUpdate, waitForEventsChange);
break;

case ActionKind.Touch:
await this.fingerTouch(navElement.button, filename, waitForScreenUpdate);
await this.fingerTouch(navElement.button, filename, waitForScreenUpdate, waitForEventsChange);
break;
default:
throw new Error("Action type not implemented");
Expand All @@ -827,10 +865,11 @@ export default class Zemu {
async runActionBatch(
navElements: INavElement[],
filename: string = "",
waitForScreenUpdate: boolean = true
waitForScreenUpdate: boolean = true,
waitForEventsChange: boolean = false,
): Promise<void> {
for (const nav of navElements) {
await this.runAction(nav, filename, waitForScreenUpdate);
await this.runAction(nav, filename, waitForScreenUpdate, waitForEventsChange);
}
}
}
2 changes: 1 addition & 1 deletion src/buttons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ const approveTapButton: IButton = {
const approveHoldButton: IButton = {
x: 335,
y: 525,
delay: 4,
delay: 5,
};

const rejectButton: IButton = {
Expand Down
2 changes: 1 addition & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
******************************************************************************* */
import { ButtonKind, type IDeviceWindow, type IStartOptions } from "./types";

export const DEFAULT_EMU_IMG = "zondax/builder-zemu:speculos-745fe46f81cfc18114b94a37beaefb58a09c0fcd";
export const DEFAULT_EMU_IMG = "zondax/builder-zemu:speculos-0fed1b8de0fbaa721f1843685385724ed6e0a4b5";

export const DEFAULT_MODEL = "nanos";
export const DEFAULT_NANO_START_TEXT = "Ready";
Expand Down
Loading

0 comments on commit 3d202d3

Please sign in to comment.