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
- ".github/workflows/core.tests.yml"
pull_request:
branches:
- "master"
paths:
- "packages/core/**"
# re-run workflow if workflow file changes
- ".github/workflows/core.tests.yml"
types:
- "ready_for_review"
- "opened"

View File

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

View File

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

View File

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

View File

@@ -17,230 +17,226 @@ 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 } from "vitest";
import { harness } from "./utils.js";
import { testCleanup, test } from "./test-override.js";
import { writeFile } from "fs/promises";
import { Page } from "playwright";
import { gt, lt } from "semver";
import { describe } from "vitest";
test("update starts downloading if version is outdated", async (t) => {
await harness(
t,
async ({ page }) => {
await page.waitForSelector("#authForm");
test("update starts downloading if version is outdated", async ({
ctx: { page },
expect,
onTestFinished
}) => {
onTestFinished(testCleanup);
t.expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page.waitForSelector("#authForm");
expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await skipDialog(page);
await skipDialog(page);
await page.waitForSelector(".ProseMirror");
await page.waitForSelector(".ProseMirror");
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /updating/i })
.waitFor({ state: "attached" });
},
{ version: "3.0.0" }
);
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /updating/i })
.waitFor({ state: "attached" });
});
test("update is only shown if version is outdated and auto updates are disabled", async (t) => {
await harness(
t,
async (ctx) => {
await ctx.app.close();
await writeFile(
ctx.configPath,
JSON.stringify({
automaticUpdates: false
})
);
test("update is only shown if version is outdated and auto updates are disabled", async ({
ctx,
expect,
onTestFinished
}) => {
onTestFinished(testCleanup);
await ctx.relaunch();
await ctx.app.close();
await writeFile(
ctx.configPath,
JSON.stringify({
automaticUpdates: false
})
);
const { page } = ctx;
await ctx.relaunch();
await page.waitForSelector("#authForm");
const { page } = ctx;
t.expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page.waitForSelector("#authForm");
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await skipDialog(page);
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await page.waitForSelector(".ProseMirror");
await skipDialog(page);
await page.waitForSelector(".ProseMirror");
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /available/i })
.waitFor({ state: "attached" });
});
describe("update to stable if it is newer", () => {
test.scoped({ options: { version: "3.0.0-beta.0" } });
test("test", async ({ ctx, expect, onTestFinished }) => {
onTestFinished(testCleanup);
await ctx.app.close();
await writeFile(
ctx.configPath,
JSON.stringify({
automaticUpdates: false,
releaseTrack: "beta"
})
);
await ctx.relaunch();
const { page } = ctx;
await page.waitForSelector("#authForm");
expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await skipDialog(page);
await page.waitForSelector(".ProseMirror");
const updateButton = page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /available/i });
await updateButton.waitFor({ state: "visible" });
const content = await updateButton.textContent();
const version = content?.split(" ")?.[0] || "";
expect(gt(version, "3.0.0-beta.0")).toBe(true);
});
});
describe("update is not available if it latest stable version is older", () => {
test.scoped({ options: { version: "99.0.0-beta.0" } });
test("test", async ({ ctx, expect, onTestFinished }) => {
onTestFinished(testCleanup);
await ctx.app.close();
await writeFile(
ctx.configPath,
JSON.stringify({
automaticUpdates: false,
releaseTrack: "beta"
})
);
await ctx.relaunch();
const { page } = ctx;
await page.waitForSelector("#authForm");
expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await skipDialog(page);
await page.waitForSelector(".ProseMirror");
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /checking for updates/i })
.waitFor({ state: "hidden" });
expect(
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /available/i })
.waitFor({ state: "attached" });
},
{ version: "3.0.0" }
);
.isHidden()
).toBe(true);
});
});
test("update to stable if it is newer", async (t) => {
await harness(
t,
async (ctx) => {
await ctx.app.close();
await writeFile(
ctx.configPath,
JSON.stringify({
automaticUpdates: false,
releaseTrack: "beta"
})
);
describe("downgrade to stable on switching to stable release track", () => {
test.scoped({ options: { version: "99.0.0-beta.0" } });
test("test", async ({ ctx, expect, onTestFinished }) => {
onTestFinished(testCleanup);
await ctx.relaunch();
await ctx.app.close();
await writeFile(
ctx.configPath,
JSON.stringify({
automaticUpdates: false,
releaseTrack: "stable"
})
);
const { page } = ctx;
await ctx.relaunch();
await page.waitForSelector("#authForm");
const { page } = ctx;
t.expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page.waitForSelector("#authForm");
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await skipDialog(page);
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await page.waitForSelector(".ProseMirror");
await skipDialog(page);
const updateButton = page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /available/i });
await updateButton.waitFor({ state: "visible" });
const content = await updateButton.textContent();
const version = content?.split(" ")?.[0] || "";
t.expect(gt(version, "3.0.0-beta.0")).toBe(true);
},
{ version: "3.0.0-beta.0" }
);
});
await page.waitForSelector(".ProseMirror");
test("update is not available if it latest stable version is older", async (t) => {
await harness(
t,
async (ctx) => {
await ctx.app.close();
await writeFile(
ctx.configPath,
JSON.stringify({
automaticUpdates: false,
releaseTrack: "beta"
})
);
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /checking for updates/i })
.waitFor({ state: "hidden" });
await ctx.relaunch();
const { page } = ctx;
await page.waitForSelector("#authForm");
t.expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await skipDialog(page);
await page.waitForSelector(".ProseMirror");
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /checking for updates/i })
.waitFor({ state: "hidden" });
t.expect(
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /available/i })
.isHidden()
).toBe(true);
},
{ version: "99.0.0-beta.0" }
);
});
test("downgrade to stable on switching to stable release track", async (t) => {
await harness(
t,
async (ctx) => {
await ctx.app.close();
await writeFile(
ctx.configPath,
JSON.stringify({
automaticUpdates: false,
releaseTrack: "stable"
})
);
await ctx.relaunch();
const { page } = ctx;
await page.waitForSelector("#authForm");
t.expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
await skipDialog(page);
await page.waitForSelector(".ProseMirror");
await page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /checking for updates/i })
.waitFor({ state: "hidden" });
const updateButton = page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /available/i });
await updateButton.waitFor({ state: "visible" });
const content = await updateButton.textContent();
const version = content?.split(" ")?.[0] || "";
t.expect(lt(version, "99.0.0-beta.0")).toBe(true);
},
{ version: "99.0.0-beta.0" }
);
const updateButton = page
.locator(".theme-scope-statusBar")
.getByRole("button", { name: /available/i });
await updateButton.waitFor({ state: "visible" });
const content = await updateButton.textContent();
const version = content?.split(" ")?.[0] || "";
expect(lt(version, "99.0.0-beta.0")).toBe(true);
});
});
async function skipDialog(page: Page) {
await page
.waitForSelector(".ReactModal__Content", {
timeout: 1000
})
.catch(() => {})
.then(async () => {
const positiveButton = page.locator(
"button[data-role='positive-button']"
);
const negativeButton = page.locator(
"button[data-role='negative-button']"
);
if (await positiveButton.isVisible()) await positiveButton.click();
else if (await negativeButton.isVisible()) await negativeButton.click();
});
try {
const dialog = page.locator(".ReactModal__Content");
const positiveButton = dialog.locator(
"button[data-role='positive-button']"
);
const negativeButton = dialog.locator(
"button[data-role='negative-button']"
);
if (await positiveButton.isVisible())
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,22 +17,24 @@ 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 } from "vitest";
import { harness } from "./utils.js";
import assert from "assert";
import { testCleanup, test } from "./test-override.js";
test("make sure app loads", async (t) => {
await harness(t, async ({ page }) => {
await page.waitForSelector("#authForm");
test("make sure app loads", async ({
ctx: { page },
expect,
onTestFinished
}) => {
onTestFinished(testCleanup);
assert.ok(
await page.getByRole("button", { name: "Create account" }).isVisible()
);
await page.waitForSelector("#authForm");
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
expect(
await page.getByRole("button", { name: "Create account" }).isVisible()
).toBe(true);
await page.waitForSelector(".ProseMirror");
});
await page
.getByRole("button", { name: "Skip & go directly to the app" })
.click();
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 { mkdir } from "fs/promises";
import { cp, readFile, writeFile } from "fs/promises";
import { fileURLToPath } from "node:url";
import path from "path";
import path, { join, resolve } from "path";
import { _electron as electron } from "playwright";
import slugify from "slugify";
import { TaskContext } from "vitest";
import { existsSync } from "fs";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
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;
page: import("playwright").Page;
configPath: string;
userDataDir: string;
outputDir: string;
relaunch: () => Promise<void>;
}
interface TestOptions {
export interface TestOptions {
version: string;
}
export async function harness(
t: TaskContext,
cb: (ctx: AppContext) => Promise<void>,
options?: TestOptions
) {
const ctx = await buildAndLaunchApp(options);
t.onTestFinished(async (result) => {
if (result.state === "fail") {
await mkdir("test-results", { recursive: true });
await ctx.page.screenshot({
path: path.join(
"test-results",
`${slugify(t.task.name)}-${process.platform}-${
process.arch
}-error.png`
)
});
}
await ctx.app.close();
});
await cb(ctx);
export interface Fixtures {
options: TestOptions;
ctx: AppContext;
}
async function buildAndLaunchApp(options?: TestOptions): Promise<AppContext> {
const productName = makeid(10);
const executablePath = await buildApp({ ...options, productName });
const { app, page, configPath } = await launchApp(executablePath);
export async function buildAndLaunchApp(
options?: TestOptions
): Promise<AppContext> {
const productName = `notesnooktest${makeid(10)}`;
const outputDir = path.join("test-artifacts", `${productName}-output`);
const executablePath = await copyBuild({
...options,
outputDir
});
const { app, page, configPath, userDataDir } = await launchApp(
executablePath,
productName
);
const ctx: AppContext = {
app,
page,
configPath,
userDataDir,
outputDir,
relaunch: async () => {
const { app, page, configPath } = await launchApp(executablePath);
const { app, page, configPath, userDataDir } = await launchApp(
executablePath,
productName
);
ctx.app = app;
ctx.page = page;
ctx.userDataDir = userDataDir;
ctx.configPath = configPath;
}
};
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({
executablePath,
args: IS_DEBUG ? [] : ["--hidden"],
env:
process.platform === "linux"
env: {
...(process.platform === "linux"
? {
...(process.env as Record<string, string>),
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 userDataDirectory = await app.evaluate((a) => {
return a.app.getPath("userData");
});
const configPath = path.join(userDataDirectory, "config.json");
const configPath = path.join(userDataDir, "UserData", "config.json");
return {
app,
page,
configPath
configPath,
userDataDir
};
}
async function buildApp({
let MAX_RETRIES = 3;
export async function buildApp(version?: string) {
if (!existsSync(SOURCE_DIR)) {
const args = [
"electron-builder",
"--dir",
`--${process.arch}`,
`--config electron-builder.config.js`,
`--c.extraMetadata.productName=${productName}`,
`--c.compression=store`,
"--publish=never"
];
if (version) args.push(`--c.extraMetadata.version=${version}`);
try {
execSync(`npx ${args.join(" ")}`, {
stdio: IS_DEBUG ? "inherit" : "ignore",
env: {
...process.env,
NOTESNOOK_STAGING: "true",
NN_PRODUCT_NAME: productName,
NN_APP_ID: `com.notesnook.test.${productName}`,
NN_OUTPUT_DIR: SOURCE_DIR
}
});
} catch (e) {
if (--MAX_RETRIES) {
console.log("retrying...");
return await buildApp(version);
} else throw e;
}
}
}
async function copyBuild({
version,
productName
outputDir
}: {
version?: string;
productName: string;
outputDir: 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"
});
return process.platform === "win32"
? await makeBuildCopyWindows(outputDir, productName, version)
: process.platform === "darwin"
? await makeBuildCopyMacOS(outputDir, productName, version)
: await makeBuildCopyLinux(outputDir, productName, version);
}
const args = [
`--config electron-builder.config.js`,
`--c.extraMetadata.productName=${productName}`,
"--publish=never"
];
if (version) args.push(`--c.extraMetadata.version=${version}`);
execSync(`npx electron-builder --dir --${process.arch} ${args.join(" ")}`, {
stdio: IS_DEBUG ? "inherit" : "ignore",
env: {
...process.env,
NOTESNOOK_STAGING: "true",
NN_BUILD_ROOT: buildRoot,
NN_PRODUCT_NAME: productName,
NN_APP_ID: `com.notesnook.test.${productName}`,
NN_OUTPUT_DIR: output
}
});
return path.join(
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,
"..",
output,
process.platform === "linux"
? 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
appDir,
productName.toLowerCase().replace(/\s+/g, "-")
);
}
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) {
let result = "";
const characters =

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,9 @@
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
"types": "./dist/types/index.d.ts",
"sideEffects": false,
"sideEffects": [
"src/overrides.ts"
],
"exports": {
".": {
"require": {
@@ -55,7 +57,7 @@
"slugify": "1.6.6",
"tree-kill": "^1.2.2",
"undici": "^7.8.0",
"vitest": "2.1.8"
"vitest": "^3.2.4"
},
"optionalDependencies": {
"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/>.
*/
import "./overrides";
import { app, BrowserWindow, nativeTheme, shell } from "electron";
import { isDevelopment } from "./utils";
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({
test: {
testTimeout: process.env.CI ? 120 * 1000 : 120 * 1000,
testTimeout: 120 * 1000,
hookTimeout: 120 * 1000,
sequence: {
concurrent: true,
shuffle: true
},
globalSetup: "./__tests__/global-setup.ts",
dir: "./__tests__/",
exclude: [
"**/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 fillPasswordDialog(this.page, userPassword);
await this.page.waitForTimeout(100);
await this.page.waitForTimeout(500);
await fillConfirmPasswordDialog(this.page, appLockPassword);
await this.page.waitForTimeout(100);
await this.page.waitForTimeout(500);
}
async disableAppLock(appLockPassword: string) {

View File

@@ -91,6 +91,7 @@ export async function fillReminderDialog(
}
await confirmDialog(dialog);
await dialog.waitFor({ state: "hidden" });
}
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) {
const dialogConfirm = dialog.locator(getTestId("dialog-yes"));
await dialogConfirm.click();
// await dialogConfirm.waitFor({ state: "detached" });
}
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();
const reminders = await app.goToReminders();
await reminders.createReminder({
...ONE_TIME_REMINDER,
date: 0
});
expect(
await app.toasts.waitForToast(
const result = await Promise.race([
reminders.createReminder({
...ONE_TIME_REMINDER,
date: 0
}),
app.toasts.waitForToast(
"Reminder time cannot be earlier than the current time."
)
).toBeTruthy();
]);
expect(result).toBeTruthy();
});
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"
});
await note?.contextMenu.openInNewTab();
await page.waitForTimeout(1000);
await note?.contextMenu.moveToTrash();

View File

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