Files
notesnook/apps/mobile/e2e/tests/utils.ts

357 lines
9.2 KiB
TypeScript
Raw Normal View History

2024-11-11 18:44:28 +05:00
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { expect as jestExpect } from "@jest/globals";
import { device as _device, expect } from "detox";
import { readFileSync } from "fs";
2025-10-28 12:00:38 +05:00
//@ts-ignore
2024-11-11 18:44:28 +05:00
import { toMatchImageSnapshot } from "jest-image-snapshot";
import type { RouteName } from "../../app/stores/use-navigation-store";
import { notesnook } from "../test.ids";
jestExpect.extend({ toMatchImageSnapshot });
const testvars = {
isFirstTest: true
};
class Element {
element: Detox.NativeElement;
constructor(public type: "id" | "text", public value: string) {
if (type == "id") {
this.element = element(by.id(value)).atIndex(0);
} else {
this.element = element(by.text(value)).atIndex(0);
}
}
isVisible(timeout?: number) {
return waitFor(this.element)
.toBeVisible()
.withTimeout(timeout || 500);
}
isNotVisible(timeout?: number) {
return waitFor(this.element)
.not.toBeVisible()
.withTimeout(timeout || 500);
}
async waitAndTap(timeout?: number) {
await waitFor(this.element)
.toBeVisible()
.withTimeout(timeout || 500);
await this.element.tap();
}
tap(point?: Detox.Point2D): Promise<void> {
return this.element.tap(point);
}
static fromId(id: string) {
return new Element("id", id);
}
static fromText(text: string) {
return new Element("text", text);
}
}
const Tests = {
awaitLaunch: async () => {
2025-10-03 14:38:34 +05:00
await device.disableSynchronization();
2024-11-11 18:44:28 +05:00
await waitFor(element(by.id(notesnook.ids.default.root)))
.toBeVisible()
2025-02-17 14:45:04 +05:00
//@ts-ignore
.withTimeout(globalThis["DEBUG_MODE"] ? 4000 : 500);
2024-11-11 18:44:28 +05:00
},
sleep: (duration: number) => {
return new Promise((resolve) =>
setTimeout(() => {
resolve(undefined);
}, duration)
);
},
fromId: Element.fromId,
fromText: Element.fromText,
async exitEditor() {
await _device.pressBack();
await _device.pressBack();
},
async createNote(_title?: string, _body?: string) {
let title = _title || "Test note description that ";
let body =
_body ||
"Test note description that is very long and should not fit in text.";
await Tests.fromId(notesnook.buttons.add).tap();
await expect(web().element(by.web.className("ProseMirror"))).toExist();
// await web().element(by.web.className("ProseMirror")).tap();
await web().element(by.web.className("ProseMirror")).typeText(body, true);
await Tests.exitEditor();
await Tests.fromText(body).isVisible();
return { title, body };
},
async navigate(screen: RouteName | ({} & string)) {
let menu = Tests.fromId(notesnook.ids.default.header.buttons.left);
await menu.waitAndTap();
2025-02-14 11:08:14 +05:00
await Tests.fromText(screen as string).waitAndTap();
2024-11-11 18:44:28 +05:00
},
async openSideMenu() {
await Tests.fromId(notesnook.ids.default.header.buttons.left).waitAndTap();
},
async prepare() {
await device.disableSynchronization();
if (testvars.isFirstTest) {
testvars.isFirstTest = false;
return await Tests.awaitLaunch();
}
await device.reverseTcpPort(8081);
await device.uninstallApp();
await device.installApp();
await device.launchApp({ newInstance: true });
await Tests.awaitLaunch();
},
async createNotebook(title = "Notebook 1", description = true) {
await Tests.sleep(1000);
const titleInput = Tests.fromId(
notesnook.ids.dialogs.notebook.inputs.title
);
await titleInput.isVisible();
await titleInput.element.typeText(title);
if (description) {
await Tests.fromId(
notesnook.ids.dialogs.notebook.inputs.description
).element.typeText(`Description of ${title}`);
}
await Tests.fromText("Add").waitAndTap();
},
async matchSnapshot(element: Element, name: string) {
let path = await element.element.takeScreenshot(name);
const bitmapBuffer = readFileSync(path);
(jestExpect(bitmapBuffer) as any).toMatchImageSnapshot({
failureThreshold: 200,
failureThresholdType: "pixel"
});
}
};
2025-10-03 14:38:34 +05:00
class TestBuilder {
private steps: (() => Promise<void> | void)[] = [];
private result: any;
2025-10-04 12:54:01 +05:00
private savedResult: any;
2025-10-03 14:38:34 +05:00
constructor() {}
2025-10-04 12:54:01 +05:00
saveResult() {
return this.addStep(() => {
this.savedResult = this.result;
});
}
2025-10-03 14:38:34 +05:00
addStep(step: () => Promise<void> | void) {
this.steps.push(step);
return this;
}
awaitLaunch() {
return this.addStep(async () => {
2025-10-04 12:54:01 +05:00
await Tests.awaitLaunch();
2025-10-03 14:38:34 +05:00
});
}
wait(duration = 500) {
2025-10-04 12:54:01 +05:00
return this.addStep(async () => {
await Tests.sleep(duration);
2025-10-03 14:38:34 +05:00
});
}
fromId(id: string) {
return this.addStep(() => {
this.result = Tests.fromId(id);
});
}
fromText(text: string) {
return this.addStep(() => {
this.result = Tests.fromText(text);
});
}
exitEditor() {
return this.addStep(async () => {
2025-10-04 12:54:01 +05:00
await Tests.exitEditor();
2025-10-03 14:38:34 +05:00
});
}
createNote(title?: string, body?: string) {
return this.addStep(async () => {
this.result = await Tests.createNote(title, body);
});
}
navigate(screen: RouteName | ({} & string)) {
return this.addStep(async () => {
2025-10-04 12:54:01 +05:00
await Tests.navigate(screen);
2025-10-03 14:38:34 +05:00
});
}
openSideMenu() {
return this.addStep(async () => {
2025-10-04 12:54:01 +05:00
await Tests.openSideMenu();
2025-10-03 14:38:34 +05:00
});
}
prepare() {
return this.addStep(async () => {
2025-10-04 12:54:01 +05:00
await Tests.prepare();
2025-10-03 14:38:34 +05:00
});
}
createNotebook(title = "Notebook 1", description = true) {
return this.addStep(async () => {
2025-10-04 12:54:01 +05:00
await Tests.createNotebook(title, description);
2025-10-03 14:38:34 +05:00
});
}
matchSnapshot(element: Element, name: string) {
return this.addStep(async () => {
2025-10-04 12:54:01 +05:00
await Tests.matchSnapshot(element, name);
2025-10-03 14:38:34 +05:00
});
}
isVisibleById(id: string, timeout?: number) {
return this.addStep(async () => {
const element = new Element("id", id);
2025-10-04 12:54:01 +05:00
await element.isVisible(timeout);
2025-10-03 14:38:34 +05:00
});
}
isVisibleByText(text: string, timeout?: number) {
return this.addStep(async () => {
const element = new Element("text", text);
2025-10-04 12:54:01 +05:00
await element.isVisible(timeout);
2025-10-03 14:38:34 +05:00
});
}
isNotVisibleById(id: string, timeout?: number) {
return this.addStep(async () => {
const element = new Element("id", id);
2025-10-04 12:54:01 +05:00
await element.isNotVisible(timeout);
2025-10-03 14:38:34 +05:00
});
}
isNotVisibleByText(text: string, timeout?: number) {
return this.addStep(async () => {
const element = new Element("text", text);
2025-10-04 12:54:01 +05:00
await element.isNotVisible(timeout);
2025-10-03 14:38:34 +05:00
});
}
waitAndTapById(id: string, timeout?: number) {
return this.addStep(async () => {
const element = new Element("id", id);
2025-10-04 12:54:01 +05:00
await element.waitAndTap(timeout);
2025-10-03 14:38:34 +05:00
});
}
waitAndTapByText(text: string, timeout?: number) {
return this.addStep(async () => {
const element = new Element("text", text);
2025-10-04 12:54:01 +05:00
await element.waitAndTap(timeout);
2025-10-03 14:38:34 +05:00
});
}
tapById(id: string, point?: Detox.Point2D) {
return this.addStep(async () => {
const element = new Element("id", id);
2025-10-04 12:54:01 +05:00
await element.tap(point);
2025-10-03 14:38:34 +05:00
});
}
tapReturnKeyById(id: string) {
return this.addStep(async () => {
const element = new Element("id", id);
await element.element.tapReturnKey();
});
}
2025-10-03 14:38:34 +05:00
tapByText(text: string, point?: Detox.Point2D) {
return this.addStep(async () => {
const element = new Element("text", text);
2025-10-04 12:54:01 +05:00
await element.tap(point);
2025-10-03 14:38:34 +05:00
});
}
2025-10-04 12:54:01 +05:00
processResult(callback: (result: any) => Promise<void>) {
2025-10-03 14:38:34 +05:00
return this.addStep(async () => {
2025-10-04 12:54:01 +05:00
if (this.savedResult) {
await callback(this.savedResult);
2025-10-03 14:38:34 +05:00
} else {
throw new Error("No result to process.");
}
});
}
2025-10-04 12:54:01 +05:00
typeTextById(id: string, text: string) {
return this.addStep(async () => {
await Element.fromId(id).element.typeText(text);
});
}
clearTextById(id: string) {
return this.addStep(async () => {
await Element.fromId(id).element.clearText();
});
}
2025-10-03 14:38:34 +05:00
pressBack(count = 1) {
return this.addStep(async () => {
for (let i = 0; i < count; i++) {
await device.pressBack();
}
});
}
longPressByText(text: string) {
return this.addStep(async () => {
const element = new Element("text", text);
await element.element.longPress();
});
}
longPressById(id: string) {
return this.addStep(async () => {
const element = new Element("id", id);
await element.element.longPress();
});
}
2025-10-03 14:38:34 +05:00
async run() {
for (const step of this.steps) {
2025-10-04 12:54:01 +05:00
const result = step.call(this);
2025-10-03 14:38:34 +05:00
if (result instanceof Promise) {
await result;
}
}
this.steps = []; // Clear steps after execution
}
static create() {
return new TestBuilder();
}
}
export { Element, Tests, TestBuilder };