Merge pull request #8372 from streetwriters/fix/web-tests

This commit is contained in:
Abdullah Atta
2025-07-21 09:54:05 +05:00
22 changed files with 1847 additions and 597 deletions

View File

@@ -10,6 +10,12 @@ on:
# re-run workflow if workflow file changes # re-run workflow if workflow file changes
- ".github/workflows/core.tests.yml" - ".github/workflows/core.tests.yml"
pull_request: pull_request:
branches:
- "master"
paths:
- "packages/core/**"
# re-run workflow if workflow file changes
- ".github/workflows/core.tests.yml"
types: types:
- "ready_for_review" - "ready_for_review"
- "opened" - "opened"

View File

@@ -10,6 +10,12 @@ on:
# re-run workflow if workflow file changes # re-run workflow if workflow file changes
- ".github/workflows/desktop.tests.yml" - ".github/workflows/desktop.tests.yml"
pull_request: pull_request:
branches:
- "master"
paths:
- "app/desktop/**"
# re-run workflow if workflow file changes
- ".github/workflows/desktop.tests.yml"
jobs: jobs:
build: build:

View File

@@ -10,6 +10,12 @@ on:
# re-run workflow if workflow file changes # re-run workflow if workflow file changes
- ".github/workflows/editor.tests.yml" - ".github/workflows/editor.tests.yml"
pull_request: pull_request:
branches:
- "master"
paths:
- "packages/editor/**"
# re-run workflow if workflow file changes
- ".github/workflows/editor.tests.yml"
types: types:
- "ready_for_review" - "ready_for_review"
- "opened" - "opened"

View File

@@ -10,6 +10,12 @@ on:
# re-run workflow if workflow file changes # re-run workflow if workflow file changes
- ".github/workflows/web.tests.yml" - ".github/workflows/web.tests.yml"
pull_request: pull_request:
branches:
- "master"
paths:
- "apps/web/**"
# re-run workflow if workflow file changes
- ".github/workflows/web.tests.yml"
types: types:
- "ready_for_review" - "ready_for_review"
- "opened" - "opened"

View File

@@ -17,19 +17,21 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { test } from "vitest"; import { testCleanup, test } from "./test-override.js";
import { harness } from "./utils.js";
import { writeFile } from "fs/promises"; import { writeFile } from "fs/promises";
import { Page } from "playwright"; import { Page } from "playwright";
import { gt, lt } from "semver"; import { gt, lt } from "semver";
import { describe } from "vitest";
test("update starts downloading if version is outdated", async ({
ctx: { page },
expect,
onTestFinished
}) => {
onTestFinished(testCleanup);
test("update starts downloading if version is outdated", async (t) => {
await harness(
t,
async ({ page }) => {
await page.waitForSelector("#authForm"); await page.waitForSelector("#authForm");
expect(
t.expect(
await page.getByRole("button", { name: "Create account" }).isVisible() await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true); ).toBe(true);
@@ -45,15 +47,15 @@ test("update starts downloading if version is outdated", async (t) => {
.locator(".theme-scope-statusBar") .locator(".theme-scope-statusBar")
.getByRole("button", { name: /updating/i }) .getByRole("button", { name: /updating/i })
.waitFor({ state: "attached" }); .waitFor({ state: "attached" });
},
{ version: "3.0.0" }
);
}); });
test("update is only shown if version is outdated and auto updates are disabled", async (t) => { test("update is only shown if version is outdated and auto updates are disabled", async ({
await harness( ctx,
t, expect,
async (ctx) => { onTestFinished
}) => {
onTestFinished(testCleanup);
await ctx.app.close(); await ctx.app.close();
await writeFile( await writeFile(
ctx.configPath, ctx.configPath,
@@ -68,7 +70,7 @@ test("update is only shown if version is outdated and auto updates are disabled"
await page.waitForSelector("#authForm"); await page.waitForSelector("#authForm");
t.expect( expect(
await page.getByRole("button", { name: "Create account" }).isVisible() await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true); ).toBe(true);
@@ -84,15 +86,13 @@ test("update is only shown if version is outdated and auto updates are disabled"
.locator(".theme-scope-statusBar") .locator(".theme-scope-statusBar")
.getByRole("button", { name: /available/i }) .getByRole("button", { name: /available/i })
.waitFor({ state: "attached" }); .waitFor({ state: "attached" });
},
{ version: "3.0.0" }
);
}); });
test("update to stable if it is newer", async (t) => { describe("update to stable if it is newer", () => {
await harness( test.scoped({ options: { version: "3.0.0-beta.0" } });
t, test("test", async ({ ctx, expect, onTestFinished }) => {
async (ctx) => { onTestFinished(testCleanup);
await ctx.app.close(); await ctx.app.close();
await writeFile( await writeFile(
ctx.configPath, ctx.configPath,
@@ -108,7 +108,7 @@ test("update to stable if it is newer", async (t) => {
await page.waitForSelector("#authForm"); await page.waitForSelector("#authForm");
t.expect( expect(
await page.getByRole("button", { name: "Create account" }).isVisible() await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true); ).toBe(true);
@@ -126,16 +126,15 @@ test("update to stable if it is newer", async (t) => {
await updateButton.waitFor({ state: "visible" }); await updateButton.waitFor({ state: "visible" });
const content = await updateButton.textContent(); const content = await updateButton.textContent();
const version = content?.split(" ")?.[0] || ""; const version = content?.split(" ")?.[0] || "";
t.expect(gt(version, "3.0.0-beta.0")).toBe(true); expect(gt(version, "3.0.0-beta.0")).toBe(true);
}, });
{ version: "3.0.0-beta.0" }
);
}); });
test("update is not available if it latest stable version is older", async (t) => { describe("update is not available if it latest stable version is older", () => {
await harness( test.scoped({ options: { version: "99.0.0-beta.0" } });
t, test("test", async ({ ctx, expect, onTestFinished }) => {
async (ctx) => { onTestFinished(testCleanup);
await ctx.app.close(); await ctx.app.close();
await writeFile( await writeFile(
ctx.configPath, ctx.configPath,
@@ -151,7 +150,7 @@ test("update is not available if it latest stable version is older", async (t) =
await page.waitForSelector("#authForm"); await page.waitForSelector("#authForm");
t.expect( expect(
await page.getByRole("button", { name: "Create account" }).isVisible() await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true); ).toBe(true);
@@ -168,21 +167,20 @@ test("update is not available if it latest stable version is older", async (t) =
.getByRole("button", { name: /checking for updates/i }) .getByRole("button", { name: /checking for updates/i })
.waitFor({ state: "hidden" }); .waitFor({ state: "hidden" });
t.expect( expect(
await page await page
.locator(".theme-scope-statusBar") .locator(".theme-scope-statusBar")
.getByRole("button", { name: /available/i }) .getByRole("button", { name: /available/i })
.isHidden() .isHidden()
).toBe(true); ).toBe(true);
}, });
{ version: "99.0.0-beta.0" }
);
}); });
test("downgrade to stable on switching to stable release track", async (t) => { describe("downgrade to stable on switching to stable release track", () => {
await harness( test.scoped({ options: { version: "99.0.0-beta.0" } });
t, test("test", async ({ ctx, expect, onTestFinished }) => {
async (ctx) => { onTestFinished(testCleanup);
await ctx.app.close(); await ctx.app.close();
await writeFile( await writeFile(
ctx.configPath, ctx.configPath,
@@ -198,7 +196,7 @@ test("downgrade to stable on switching to stable release track", async (t) => {
await page.waitForSelector("#authForm"); await page.waitForSelector("#authForm");
t.expect( expect(
await page.getByRole("button", { name: "Create account" }).isVisible() await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true); ).toBe(true);
@@ -221,26 +219,24 @@ test("downgrade to stable on switching to stable release track", async (t) => {
await updateButton.waitFor({ state: "visible" }); await updateButton.waitFor({ state: "visible" });
const content = await updateButton.textContent(); const content = await updateButton.textContent();
const version = content?.split(" ")?.[0] || ""; const version = content?.split(" ")?.[0] || "";
t.expect(lt(version, "99.0.0-beta.0")).toBe(true); expect(lt(version, "99.0.0-beta.0")).toBe(true);
}, });
{ version: "99.0.0-beta.0" }
);
}); });
async function skipDialog(page: Page) { async function skipDialog(page: Page) {
await page try {
.waitForSelector(".ReactModal__Content", { const dialog = page.locator(".ReactModal__Content");
timeout: 1000 const positiveButton = dialog.locator(
})
.catch(() => {})
.then(async () => {
const positiveButton = page.locator(
"button[data-role='positive-button']" "button[data-role='positive-button']"
); );
const negativeButton = page.locator( const negativeButton = dialog.locator(
"button[data-role='negative-button']" "button[data-role='negative-button']"
); );
if (await positiveButton.isVisible()) await positiveButton.click(); if (await positiveButton.isVisible())
else if (await negativeButton.isVisible()) await negativeButton.click(); await positiveButton.click({ timeout: 1000 });
}); else if (await negativeButton.isVisible())
await negativeButton.click({ timeout: 1000 });
} catch (e) {
// ignore error
}
} }

View File

@@ -0,0 +1,24 @@
/*
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 { buildApp } from "./utils";
export default async function setup() {
await buildApp();
}

View File

@@ -17,17 +17,20 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { test } from "vitest"; import { testCleanup, test } from "./test-override.js";
import { harness } from "./utils.js";
import assert from "assert"; test("make sure app loads", async ({
ctx: { page },
expect,
onTestFinished
}) => {
onTestFinished(testCleanup);
test("make sure app loads", async (t) => {
await harness(t, async ({ page }) => {
await page.waitForSelector("#authForm"); await page.waitForSelector("#authForm");
assert.ok( expect(
await page.getByRole("button", { name: "Create account" }).isVisible() await page.getByRole("button", { name: "Create account" }).isVisible()
); ).toBe(true);
await page await page
.getByRole("button", { name: "Skip & go directly to the app" }) .getByRole("button", { name: "Skip & go directly to the app" })
@@ -35,4 +38,3 @@ test("make sure app loads", async (t) => {
await page.waitForSelector(".ProseMirror"); await page.waitForSelector(".ProseMirror");
}); });
});

View File

@@ -0,0 +1,64 @@
/*
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 { test as vitestTest, TestContext } from "vitest";
import { buildAndLaunchApp, Fixtures, TestOptions } from "./utils";
import { mkdir, rm } from "fs/promises";
import path from "path";
import slugify from "slugify";
export const test = vitestTest.extend<Fixtures>({
options: { version: "3.0.0" } as TestOptions,
ctx: async ({ options }, use) => {
const ctx = await buildAndLaunchApp(options);
await use(ctx);
}
});
export async function testCleanup(context: TestContext) {
const ctx = (context.task.context as unknown as Fixtures).ctx;
if (context.task.result?.state === "fail") {
await mkdir("test-results", { recursive: true });
await ctx.page.screenshot({
path: path.join(
"test-results",
`${slugify(context.task.name)}-${process.platform}-${
process.arch
}-error.png`
)
});
}
await ctx.app.close();
await rm(ctx.userDataDir, {
force: true,
recursive: true,
maxRetries: 3,
retryDelay: 5000
}).catch(() => {
/*ignore */
});
await rm(ctx.outputDir, {
force: true,
recursive: true,
maxRetries: 3,
retryDelay: 5000
}).catch(() => {
/*ignore */
});
}

View File

@@ -18,146 +18,235 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { execSync } from "child_process"; import { execSync } from "child_process";
import { mkdir } from "fs/promises"; import { cp, readFile, writeFile } from "fs/promises";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import path from "path"; import path, { join, resolve } from "path";
import { _electron as electron } from "playwright"; import { _electron as electron } from "playwright";
import slugify from "slugify"; import { existsSync } from "fs";
import { TaskContext } from "vitest";
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
const IS_DEBUG = process.env.NN_DEBUG === "true" || process.env.CI === "true"; const IS_DEBUG = process.env.NN_DEBUG === "true" || process.env.CI === "true";
const productName = `NotesnookTestHarness`;
const SOURCE_DIR = resolve("output", productName);
interface AppContext { export interface AppContext {
app: import("playwright").ElectronApplication; app: import("playwright").ElectronApplication;
page: import("playwright").Page; page: import("playwright").Page;
configPath: string; configPath: string;
userDataDir: string;
outputDir: string;
relaunch: () => Promise<void>; relaunch: () => Promise<void>;
} }
interface TestOptions { export interface TestOptions {
version: string; version: string;
} }
export async function harness( export interface Fixtures {
t: TaskContext, options: TestOptions;
cb: (ctx: AppContext) => Promise<void>, ctx: AppContext;
}
export async function buildAndLaunchApp(
options?: TestOptions options?: TestOptions
) { ): Promise<AppContext> {
const ctx = await buildAndLaunchApp(options); const productName = `notesnooktest${makeid(10)}`;
const outputDir = path.join("test-artifacts", `${productName}-output`);
t.onTestFinished(async (result) => { const executablePath = await copyBuild({
if (result.state === "fail") { ...options,
await mkdir("test-results", { recursive: true }); outputDir
await ctx.page.screenshot({
path: path.join(
"test-results",
`${slugify(t.task.name)}-${process.platform}-${
process.arch
}-error.png`
)
}); });
} const { app, page, configPath, userDataDir } = await launchApp(
await ctx.app.close(); executablePath,
}); productName
);
await cb(ctx);
}
async function buildAndLaunchApp(options?: TestOptions): Promise<AppContext> {
const productName = makeid(10);
const executablePath = await buildApp({ ...options, productName });
const { app, page, configPath } = await launchApp(executablePath);
const ctx: AppContext = { const ctx: AppContext = {
app, app,
page, page,
configPath, configPath,
userDataDir,
outputDir,
relaunch: async () => { relaunch: async () => {
const { app, page, configPath } = await launchApp(executablePath); const { app, page, configPath, userDataDir } = await launchApp(
executablePath,
productName
);
ctx.app = app; ctx.app = app;
ctx.page = page; ctx.page = page;
ctx.userDataDir = userDataDir;
ctx.configPath = configPath; ctx.configPath = configPath;
} }
}; };
return ctx; return ctx;
} }
async function launchApp(executablePath: string) { async function launchApp(executablePath: string, packageName: string) {
const userDataDir = resolve(
__dirname,
"..",
"test-artifacts",
"user_data_dirs",
packageName
);
const app = await electron.launch({ const app = await electron.launch({
executablePath, executablePath,
args: IS_DEBUG ? [] : ["--hidden"], args: IS_DEBUG ? [] : ["--hidden"],
env: env: {
process.platform === "linux" ...(process.platform === "linux"
? { ? {
...(process.env as Record<string, string>), ...(process.env as Record<string, string>),
APPIMAGE: "true" APPIMAGE: "true"
} }
: (process.env as Record<string, string>) : (process.env as Record<string, string>)),
CUSTOM_USER_DATA_DIR: userDataDir
}
}); });
const page = await app.firstWindow(); const page = await app.firstWindow();
const userDataDirectory = await app.evaluate((a) => { const configPath = path.join(userDataDir, "UserData", "config.json");
return a.app.getPath("userData");
});
const configPath = path.join(userDataDirectory, "config.json");
return { return {
app, app,
page, page,
configPath configPath,
userDataDir
}; };
} }
async function buildApp({ let MAX_RETRIES = 3;
version, export async function buildApp(version?: string) {
productName if (!existsSync(SOURCE_DIR)) {
}: {
version?: string;
productName: string;
}) {
const buildRoot = path.join("test-artifacts", `${productName}-build`);
const output = path.join("test-artifacts", `${productName}-output`);
execSync(`npm run release -- --root ${buildRoot} --skip-tsc-build`, {
stdio: IS_DEBUG ? "inherit" : "ignore"
});
const args = [ const args = [
"electron-builder",
"--dir",
`--${process.arch}`,
`--config electron-builder.config.js`, `--config electron-builder.config.js`,
`--c.extraMetadata.productName=${productName}`, `--c.extraMetadata.productName=${productName}`,
`--c.compression=store`,
"--publish=never" "--publish=never"
]; ];
if (version) args.push(`--c.extraMetadata.version=${version}`); if (version) args.push(`--c.extraMetadata.version=${version}`);
try {
execSync(`npx electron-builder --dir --${process.arch} ${args.join(" ")}`, { execSync(`npx ${args.join(" ")}`, {
stdio: IS_DEBUG ? "inherit" : "ignore", stdio: IS_DEBUG ? "inherit" : "ignore",
env: { env: {
...process.env, ...process.env,
NOTESNOOK_STAGING: "true", NOTESNOOK_STAGING: "true",
NN_BUILD_ROOT: buildRoot,
NN_PRODUCT_NAME: productName, NN_PRODUCT_NAME: productName,
NN_APP_ID: `com.notesnook.test.${productName}`, NN_APP_ID: `com.notesnook.test.${productName}`,
NN_OUTPUT_DIR: output NN_OUTPUT_DIR: SOURCE_DIR
} }
}); });
} catch (e) {
if (--MAX_RETRIES) {
console.log("retrying...");
return await buildApp(version);
} else throw e;
}
}
}
return path.join( async function copyBuild({
version,
outputDir
}: {
version?: string;
outputDir: string;
}) {
return process.platform === "win32"
? await makeBuildCopyWindows(outputDir, productName, version)
: process.platform === "darwin"
? await makeBuildCopyMacOS(outputDir, productName, version)
: await makeBuildCopyLinux(outputDir, productName, version);
}
async function makeBuildCopyLinux(
outputDir: string,
productName: string,
version?: string
) {
const platformDir =
process.arch === "arm64" ? "linux-arm64-unpacked" : "linux-unpacked";
const appDir = await makeBuildCopy(
outputDir,
platformDir,
"resources",
version
);
return resolve(
__dirname, __dirname,
"..", "..",
output, appDir,
process.platform === "linux" productName.toLowerCase().replace(/\s+/g, "-")
? process.arch === "arm64"
? "linux-arm64-unpacked"
: "linux-unpacked"
: process.platform === "darwin"
? process.arch === "arm64"
? `mac-arm64/${productName}.app/Contents/MacOS/`
: `mac/${productName}.app/Contents/MacOS/`
: "win-unpacked",
process.platform === "win32" ? `${productName}.exe` : productName
); );
} }
async function makeBuildCopyWindows(
outputDir: string,
productName: string,
version?: string
) {
const platformDir =
process.arch === "arm64" ? "win-arm64-unpacked" : "win-unpacked";
const appDir = await makeBuildCopy(
outputDir,
platformDir,
"resources",
version
);
return resolve(__dirname, "..", appDir, `${productName}.exe`);
}
async function makeBuildCopyMacOS(
outputDir: string,
productName: string,
version?: string
) {
const platformDir = process.arch === "arm64" ? "mac-arm64" : "mac";
const appDir = await makeBuildCopy(
outputDir,
platformDir,
join(`${productName}.app`, "Contents", "Resources"),
version
);
return resolve(
__dirname,
"..",
appDir,
`${productName}.app`,
"Contents",
"MacOS",
productName
);
}
async function makeBuildCopy(
outputDir: string,
platformDir: string,
resourcesDir: string,
version?: string
) {
const appDir = outputDir;
await cp(join(SOURCE_DIR, platformDir), outputDir, {
recursive: true,
preserveTimestamps: true,
verbatimSymlinks: true,
dereference: false,
force: true
});
const packageJsonPath = join(appDir, resourcesDir, "app", "package.json");
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf-8"));
if (version) {
packageJson.version = version;
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
}
return appDir;
}
function makeid(length: number) { function makeid(length: number) {
let result = ""; let result = "";
const characters = const characters =

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,9 @@
"main": "./dist/cjs/index.js", "main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js", "module": "./dist/esm/index.js",
"types": "./dist/types/index.d.ts", "types": "./dist/types/index.d.ts",
"sideEffects": false, "sideEffects": [
"src/overrides.ts"
],
"exports": { "exports": {
".": { ".": {
"require": { "require": {
@@ -55,7 +57,7 @@
"slugify": "1.6.6", "slugify": "1.6.6",
"tree-kill": "^1.2.2", "tree-kill": "^1.2.2",
"undici": "^7.8.0", "undici": "^7.8.0",
"vitest": "2.1.8" "vitest": "^3.2.4"
}, },
"optionalDependencies": { "optionalDependencies": {
"dmg-license": "^1.0.11" "dmg-license": "^1.0.11"

View File

@@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import "./overrides";
import { app, BrowserWindow, nativeTheme, shell } from "electron"; import { app, BrowserWindow, nativeTheme, shell } from "electron";
import { isDevelopment } from "./utils"; import { isDevelopment } from "./utils";
import { registerProtocol, PROTOCOL_URL } from "./utils/protocol"; import { registerProtocol, PROTOCOL_URL } from "./utils/protocol";

View File

@@ -0,0 +1,35 @@
/*
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 { app } from "electron";
import path from "path";
if (process.env.CUSTOM_USER_DATA_DIR) {
app.setPath(
"appData",
path.join(process.env.CUSTOM_USER_DATA_DIR, "AppData")
);
app.setPath(
"userData",
path.join(process.env.CUSTOM_USER_DATA_DIR, "UserData")
);
app.setPath(
"documents",
path.join(process.env.CUSTOM_USER_DATA_DIR, "Documents")
);
}

View File

@@ -21,11 +21,13 @@ import { defineConfig } from "vitest/config";
export default defineConfig({ export default defineConfig({
test: { test: {
testTimeout: process.env.CI ? 120 * 1000 : 120 * 1000, testTimeout: 120 * 1000,
hookTimeout: 120 * 1000,
sequence: { sequence: {
concurrent: true, concurrent: true,
shuffle: true shuffle: true
}, },
globalSetup: "./__tests__/global-setup.ts",
dir: "./__tests__/", dir: "./__tests__/",
exclude: [ exclude: [
"**/node_modules/**", "**/node_modules/**",

View File

@@ -1 +1 @@
This is Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1An edit I made An edit I madeThis is Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1

View File

@@ -1 +1 @@
This is Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1An edit I made An edit I madeThis is Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1

View File

@@ -1 +1 @@
This is Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1An edit I made An edit I madeThis is Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1

View File

@@ -162,9 +162,9 @@ export class SettingsViewModel {
await appLockSwitch.click(); await appLockSwitch.click();
await fillPasswordDialog(this.page, userPassword); await fillPasswordDialog(this.page, userPassword);
await this.page.waitForTimeout(100); await this.page.waitForTimeout(500);
await fillConfirmPasswordDialog(this.page, appLockPassword); await fillConfirmPasswordDialog(this.page, appLockPassword);
await this.page.waitForTimeout(100); await this.page.waitForTimeout(500);
} }
async disableAppLock(appLockPassword: string) { async disableAppLock(appLockPassword: string) {

View File

@@ -91,6 +91,7 @@ export async function fillReminderDialog(
} }
await confirmDialog(dialog); await confirmDialog(dialog);
await dialog.waitFor({ state: "hidden" });
} }
export async function fillItemDialog(page: Page, item: Item) { export async function fillItemDialog(page: Page, item: Item) {
@@ -133,7 +134,6 @@ export async function fillConfirmPasswordDialog(page: Page, password: string) {
export async function confirmDialog(dialog: Locator) { export async function confirmDialog(dialog: Locator) {
const dialogConfirm = dialog.locator(getTestId("dialog-yes")); const dialogConfirm = dialog.locator(getTestId("dialog-yes"));
await dialogConfirm.click(); await dialogConfirm.click();
// await dialogConfirm.waitFor({ state: "detached" });
} }
export async function denyDialog(page: Page) { export async function denyDialog(page: Page) {

View File

@@ -53,16 +53,17 @@ test("adding a one-time reminder before current time should not be possible", as
await app.goto(); await app.goto();
const reminders = await app.goToReminders(); const reminders = await app.goToReminders();
await reminders.createReminder({ const result = await Promise.race([
reminders.createReminder({
...ONE_TIME_REMINDER, ...ONE_TIME_REMINDER,
date: 0 date: 0
}); }),
app.toasts.waitForToast(
expect(
await app.toasts.waitForToast(
"Reminder time cannot be earlier than the current time." "Reminder time cannot be earlier than the current time."
) )
).toBeTruthy(); ]);
expect(result).toBeTruthy();
}); });
for (const recurringMode of ["Daily", "Weekly", "Monthly"] as const) { for (const recurringMode of ["Daily", "Weekly", "Monthly"] as const) {

View File

@@ -455,6 +455,7 @@ test("if note is active in multiple tabs, moving the note to trash should close
title: "Note 1" title: "Note 1"
}); });
await note?.contextMenu.openInNewTab(); await note?.contextMenu.openInNewTab();
await page.waitForTimeout(1000);
await note?.contextMenu.moveToTrash(); await note?.contextMenu.moveToTrash();

View File

@@ -5,7 +5,8 @@
"outputs": [ "outputs": [
"{projectRoot}/build", "{projectRoot}/build",
"{projectRoot}/dist", "{projectRoot}/dist",
"{projectRoot}/languages" "{projectRoot}/languages",
"{projectRoot}/src/extensions/code-block/languages"
], ],
"cache": true "cache": true
}, },