Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 23 additions & 17 deletions e2e/keep-alive.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,21 @@ import { test, expect } from "./fixtures";
import { openOptionsPage } from "./utils";
import type { CDPSession } from "@playwright/test";

const KEEP_ALIVE_LABEL = "Keep Background and Scheduled Scripts Alive";
const SERVICE_WORKER_URL = "/service_worker.js";
const HEARTBEAT_VALIDATION_WINDOW_MS = 31_000;

const openRuntimeSettings = async (context: Parameters<typeof openOptionsPage>[0], extensionId: string) => {
const page = await openOptionsPage(context, extensionId);
await page
.getByTestId("view-toggle")
.or(page.getByTestId("mobile-search"))
.first()
.waitFor({ state: "visible", timeout: 30_000 });
await page.goto(`chrome-extension://${extensionId}/src/options.html#/settings`);
await expect(page.getByTestId("setting-page")).toBeVisible({ timeout: 20_000 });
return page;
};

type CdpTargetMessage = {
sessionId: string;
message: string;
Expand Down Expand Up @@ -53,17 +64,14 @@ const sendTargetCommand = async (

test.describe("Chrome MV3 service worker keep-alive", () => {
test("offscreen runtime heartbeat keeps the service worker active", async ({ context, extensionId }) => {
const optionsPage = await openOptionsPage(context, extensionId);
const cdp = await context.newCDPSession(optionsPage);
const optionsPage = await openRuntimeSettings(context, extensionId);

try {
await optionsPage.goto(`chrome-extension://${extensionId}/src/options.html#/settings`);
const label = optionsPage.getByText(KEEP_ALIVE_LABEL, { exact: true });
await label.scrollIntoViewIfNeeded();

const keepAliveSwitch = label.locator("xpath=../..").getByRole("switch");
const keepAliveSwitch = optionsPage.getByTestId("keep-alive-switch");
await keepAliveSwitch.scrollIntoViewIfNeeded();
await expect(keepAliveSwitch).toBeVisible();
await expect(keepAliveSwitch).toHaveAttribute("aria-checked", "false");
const cdp = await context.newCDPSession(optionsPage);

await expect
.poll(
Expand Down Expand Up @@ -93,24 +101,22 @@ test.describe("Chrome MV3 service worker keep-alive", () => {
});

test("disabling the setting allows the service worker to become idle", async ({ context, extensionId }) => {
const optionsPage = await openOptionsPage(context, extensionId);
const cdp = await context.newCDPSession(optionsPage);
const optionsPage = await openRuntimeSettings(context, extensionId);
let cdp: CDPSession | undefined;
let offscreenSessionId: string | undefined;
let nextCommandId = 1;

try {
await optionsPage.goto(`chrome-extension://${extensionId}/src/options.html#/settings`);
const label = optionsPage.getByText(KEEP_ALIVE_LABEL, { exact: true });
await label.scrollIntoViewIfNeeded();

const keepAliveSwitch = label.locator("xpath=../..").getByRole("switch");
const keepAliveSwitch = optionsPage.getByTestId("keep-alive-switch");
await keepAliveSwitch.scrollIntoViewIfNeeded();
await expect(keepAliveSwitch).toBeVisible();
await expect(keepAliveSwitch).toHaveAttribute("aria-checked", "false");
cdp = await context.newCDPSession(optionsPage);

await expect
.poll(
async () => {
const { targetInfos } = await cdp.send("Target.getTargets");
const { targetInfos } = await cdp!.send("Target.getTargets");
return targetInfos.some((target) => target.url.endsWith("/src/offscreen.html"));
},
{ timeout: 15_000 }
Expand Down Expand Up @@ -168,7 +174,7 @@ test.describe("Chrome MV3 service worker keep-alive", () => {
.toBe(true);
} finally {
if (offscreenSessionId) {
await cdp.send("Target.detachFromTarget", { sessionId: offscreenSessionId });
await cdp!.send("Target.detachFromTarget", { sessionId: offscreenSessionId });
}
if (!optionsPage.isClosed()) await optionsPage.close();
}
Expand Down
9 changes: 2 additions & 7 deletions e2e/popup.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import { test, expect } from "./fixtures";
import { openPopupPage } from "./utils";

// new-ui popup(shadcn):标题 h1、全局 Radix Switch、Radix Accordion 分组、
// 图标按钮(aria-label 设置/更多菜单)、Radix DropdownMenu(role=menuitem)。
// new-ui popup(shadcn):全局 Radix Switch、Radix Accordion 分组、图标按钮(aria-label 设置/更多菜单)
// Radix DropdownMenu(role=menuitem)。
test.describe("Popup 页面", () => {
test("应加载并显示 ScriptCat 标题", async ({ context, extensionId }) => {
const page = await openPopupPage(context, extensionId);
await expect(page.getByText("ScriptCat", { exact: true })).toBeVisible({ timeout: 10_000 });
});

test("应显示全局脚本启用/禁用开关", async ({ context, extensionId }) => {
const page = await openPopupPage(context, extensionId);
// 顶部全局开关为 Radix Switch(role=switch)
Expand Down
18 changes: 0 additions & 18 deletions e2e/script-editor.spec.ts

This file was deleted.

7 changes: 0 additions & 7 deletions e2e/script-management.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,6 @@ async function createScriptAndGoToList(context: BrowserContext, extensionId: str
}

test.describe("脚本管理", () => {
test("创建脚本后应出现在列表中", async ({ context, extensionId }) => {
const page = await createScriptAndGoToList(context, extensionId);
// 列表非空(无空状态)
await expect(page.getByTestId("script-list-empty")).toHaveCount(0, { timeout: 10_000 });
await expect(page.getByRole("switch").first()).toBeVisible({ timeout: 10_000 });
});

test("应能切换脚本的启用/禁用", async ({ context, extensionId }) => {
const page = await createScriptAndGoToList(context, extensionId);

Expand Down
12 changes: 0 additions & 12 deletions packages/filesystem/s3/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,6 @@ describe("S3Error", () => {
expect(err.message).toBe("The specified key does not exist");
expect(err.statusCode).toBe(404);
});

it("应当可被 try/catch 捕获并通过 instanceof 判断", () => {
try {
throw new S3Error("AccessDenied", "Access Denied", 403);
} catch (e) {
expect(e).toBeInstanceOf(S3Error);
if (e instanceof S3Error) {
expect(e.code).toBe("AccessDenied");
expect(e.statusCode).toBe(403);
}
}
});
});

// ---- S3Client 构造函数与 getter 方法 ----
Expand Down
22 changes: 0 additions & 22 deletions packages/filesystem/s3/s3.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,21 +123,6 @@ describe("S3FileSystem", () => {

// ---- open ----
describe("open", () => {
it("应当返回 S3FileReader", async () => {
const fileInfo: FileInfo = {
name: "test.txt",
path: "/docs",
size: 100,
digest: "abc",
createtime: 1000,
updatetime: 2000,
};
const reader = await fs.open(fileInfo);

expect(reader).toBeDefined();
expect(reader.read).toBeTypeOf("function");
});

it("S3FileReader.read 应调用 client.request GET", async () => {
const fileInfo: FileInfo = {
name: "hello.txt",
Expand Down Expand Up @@ -205,13 +190,6 @@ describe("S3FileSystem", () => {

// ---- create ----
describe("create", () => {
it("应当返回 S3FileWriter", async () => {
const writer = await fs.create("test.txt");

expect(writer).toBeDefined();
expect(writer.write).toBeTypeOf("function");
});

it("S3FileWriter.write 应调用 client.request PUT", async () => {
(mockClient.request as ReturnType<typeof vi.fn>).mockResolvedValue(createMockResponse({ ok: true }));

Expand Down
39 changes: 1 addition & 38 deletions packages/message/message_queue.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { MessageQueue, MessageQueueGroup, type IMessageQueue } from "./message_queue";
import { MessageQueue, type IMessageQueue } from "./message_queue";

const nextTick = () => Promise.resolve().then(() => {});

Expand All @@ -16,11 +16,6 @@ describe("MessageQueueGroup", () => {
});

describe("基本功能测试", () => {
it.concurrent("应该能够创建分组", () => {
const group = messageQueue.group("api-group");
expect(group).toBeInstanceOf(MessageQueueGroup);
});

it.concurrent("应该能够在分组中订阅和发布消息", () => {
const group = messageQueue.group("api-sendBasic");
const handler = vi.fn();
Expand Down Expand Up @@ -269,36 +264,4 @@ describe("MessageQueueGroup", () => {
expect(handler).toHaveBeenCalledTimes(1);
});
});

describe("边界情况测试", () => {
it.concurrent("没有中间件的分组应该正常工作", () => {
const group = messageQueue.group("api-groupNoMiddleware");
const handler = vi.fn();

group.subscribe("test-groupNoMiddleware", handler);
group.emit("test-groupNoMiddleware", { data: "test-groupNoMiddleware" });

expect(handler).toHaveBeenCalledWith({ data: "test-groupNoMiddleware" });
});

it.concurrent("应该能够处理复杂的数据类型", () => {
const group = messageQueue.group("api-complexPayload");
const handler = vi.fn();

const complexData = {
array: [1, 2, 3],
object: { nested: true },
number: 42,
string: "test-complexPayload",
boolean: true,
null: null,
undefined: undefined,
};

group.subscribe("test-complexPayload", handler);
group.emit("test-complexPayload", complexData);

expect(handler).toHaveBeenCalledWith(complexData);
});
});
});
58 changes: 0 additions & 58 deletions packages/message/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,21 +66,6 @@ describe("Server", () => {
expect(response.data).toBe("sync response");
});

it.concurrent("应该能够处理异步函数", async () => {
const mockHandler = vi.fn().mockResolvedValue("async response");

server.on("on-async", mockHandler);

const response = await client.sendMessage({
action: "api/on-async",
data: { param: "value-async" },
});

expect(mockHandler).toHaveBeenCalledWith({ param: "value-async" }, expect.any(SenderRuntime));
expect(response.code).toBe(0);
expect(response.data).toBe("async response");
});

it.concurrent("应该能够处理函数抛出的错误", async () => {
const error = new Error("test error");
const mockHandler = vi.fn().mockImplementation(() => {
Expand Down Expand Up @@ -397,25 +382,6 @@ describe("Server", () => {
expect(handler).toHaveBeenCalledTimes(1);
});

it("没有中间件的 Group 应该正常工作", async () => {
const group = server.group("api");

const handler = vi.fn(async (params: any) => {
return { data: params };
});

group.on("nomiddle", handler);

const response = await client.sendMessage({
action: "api/api/nomiddle",
data: { message: "hello" },
});

expect(response.code).toBe(0);
expect(response.data).toEqual({ data: { message: "hello" } });
expect(handler).toHaveBeenCalledTimes(1);
});

it("中间件应该能够处理异步错误", async () => {
const errorMiddleware = vi.fn(async (params: any, con: any, next: any) => {
if (params.throwError) {
Expand Down Expand Up @@ -655,30 +621,6 @@ describe("Server", () => {
expect(response.data).toBe("empty response");
});

it.concurrent("应该能够处理复杂的数据类型", async () => {
const complexData = {
array: [1, 2, 3],
object: { nested: true },
number: 42,
string: "test",
boolean: true,
null: null,
undefined: undefined,
};

const mockHandler = vi.fn().mockImplementation((params) => params);

server.on("on-complex", mockHandler);

const response = await client.sendMessage({
action: "api/on-complex",
data: complexData,
});

expect(response.code).toBe(0);
expect(response.data).toEqual(complexData);
});

it.concurrent("应该能够处理返回 undefined 的函数", async () => {
const mockHandler = vi.fn().mockReturnValue(undefined);

Expand Down
40 changes: 0 additions & 40 deletions packages/message/window_message.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,6 @@ describe("ServiceWorkerMessageSend", () => {
});

describe("ServiceWorkerClientMessage", () => {
it("controller 可用时直接使用", () => {
const clientMsg = new ServiceWorkerClientMessage();

expect((clientMsg as any).sw).not.toBeNull();
expect((clientMsg as any).sw.postMessage).toBe(swPostMessageMock);
});

it("controller 为 null 时通过 ready 获取 active SW", async () => {
const readyPostMessage = vi.fn();
Object.defineProperty(navigator, "serviceWorker", {
Expand Down Expand Up @@ -334,19 +327,6 @@ describe("ServiceWorkerMessageSend ↔ ServiceWorkerClientMessage 双向通信",
return { swSend, clientMsg };
}

it("sendMessage: client→SW 请求并收到响应", async () => {
const { swSend, clientMsg } = createWiredPair();

// SW 端注册处理器
swSend.onMessage((msg: any, sendResponse: any) => {
sendResponse({ code: 0, data: (msg.data as string) + " world" });
return true;
});

const result = await clientMsg.sendMessage({ action: "test/echo", data: "hello" });
expect(result).toEqual({ code: 0, data: "hello world" });
});

it("connect: 建立连接后双向通信", async () => {
const { swSend, clientMsg } = createWiredPair();

Expand Down Expand Up @@ -402,26 +382,6 @@ describe("ServiceWorkerMessageSend ↔ ServiceWorkerClientMessage 双向通信",
expect(serverDisconnected).toBe(true);
});

it("sendMessage: 支持传输复杂对象(模拟结构化克隆场景)", async () => {
const { swSend, clientMsg } = createWiredPair();

swSend.onMessage((msg: any, sendResponse: any) => {
// 原样返回,验证数据完整性
sendResponse({ code: 0, data: msg.data });
return true;
});

const complexData = {
array: [1, 2, 3],
nested: { a: { b: "deep" } },
nullVal: null,
boolVal: true,
};

const result = await clientMsg.sendMessage({ action: "test/complex", data: complexData });
expect((result as any).data).toEqual(complexData);
});

it("与 Server 集成: forwardMessage 路径", async () => {
const swSend = new ServiceWorkerMessageSend();
const clientMsg = new ServiceWorkerClientMessage();
Expand Down
Loading
Loading