Skip to content
40 changes: 26 additions & 14 deletions e2e/popup-matching-regressions.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { test, expect } from "./fixtures";
import { test, expect, startMockServer, type MockServer } from "./server-fixtures";
import { installScriptByCode } from "./utils";
import type { Page } from "@playwright/test";

Expand All @@ -7,19 +7,19 @@ function scriptCode(name: string, rule: "match" | "include") {
// @name ${name}
// @namespace issue-1591-e2e
// @version 1.0.0
// @${rule} https://example.com/*
// @${rule} http://sitea.test/*
// @grant none
// ==/UserScript==
console.log("${name}");`;
}

async function getTargetTab(extensionPage: Page) {
return extensionPage.evaluate(async () => {
async function getTargetTab(extensionPage: Page, targetUrl: string) {
return extensionPage.evaluate(async (targetUrl) => {
const tabs = await chrome.tabs.query({});
const tab = tabs.find((item) => item.url?.startsWith("https://example.com/"));
const tab = tabs.find((item) => item.url === targetUrl);
if (!tab?.id || !tab.url) throw new Error("target tab not found");
return { tabId: tab.id, url: tab.url };
});
}, targetUrl);
}

async function verifyExcludeRoundTrip(
Expand All @@ -41,7 +41,7 @@ async function verifyExcludeRoundTrip(

const exclude = await chrome.runtime.sendMessage({
action: "serviceWorker/script/excludeUrl",
data: { uuid: script.uuid, excludePattern: "*://example.com/*", remove: false },
data: { uuid: script.uuid, excludePattern: "*://sitea.test/*", remove: false },
});
if (exclude.code) throw new Error(`exclude failed: ${JSON.stringify(exclude)}`);

Expand All @@ -51,7 +51,7 @@ async function verifyExcludeRoundTrip(

const unexclude = await chrome.runtime.sendMessage({
action: "serviceWorker/script/excludeUrl",
data: { uuid: script.uuid, excludePattern: "*://example.com/*", remove: true },
data: { uuid: script.uuid, excludePattern: "*://sitea.test/*", remove: true },
});
if (unexclude.code) throw new Error(`unexclude failed: ${JSON.stringify(unexclude)}`);

Expand All @@ -66,6 +66,18 @@ async function verifyExcludeRoundTrip(
}

test.describe("Issue 1591: Popup exclusion regression", () => {
let server: MockServer;
let targetUrl: string;

test.beforeEach(async () => {
server = await startMockServer();
targetUrl = server.url("sitea.test", "/page");
});

test.afterEach(async () => {
await server.close();
});

test("@match script remains visible and reversible after excluding the current site", async ({
context,
extensionId,
Expand All @@ -75,9 +87,9 @@ test.describe("Issue 1591: Popup exclusion regression", () => {
const target = await context.newPage();
const extensionPage = await context.newPage();
try {
await target.goto("https://example.com/", { waitUntil: "domcontentloaded" });
await target.goto(targetUrl, { waitUntil: "domcontentloaded" });
await extensionPage.goto(`chrome-extension://${extensionId}/src/options.html`);
const result = await verifyExcludeRoundTrip(extensionPage, await getTargetTab(extensionPage), name);
const result = await verifyExcludeRoundTrip(extensionPage, await getTargetTab(extensionPage, targetUrl), name);
expect(result).toEqual({ excludedIsEffective: false, restoredIsEffective: true });
} finally {
await extensionPage.close();
Expand All @@ -94,9 +106,9 @@ test.describe("Issue 1591: Popup exclusion regression", () => {
const target = await context.newPage();
const extensionPage = await context.newPage();
try {
await target.goto("https://example.com/", { waitUntil: "domcontentloaded" });
await target.goto(targetUrl, { waitUntil: "domcontentloaded" });
await extensionPage.goto(`chrome-extension://${extensionId}/src/options.html`);
const result = await verifyExcludeRoundTrip(extensionPage, await getTargetTab(extensionPage), name);
const result = await verifyExcludeRoundTrip(extensionPage, await getTargetTab(extensionPage, targetUrl), name);
expect(result).toEqual({ excludedIsEffective: false, restoredIsEffective: true });
} finally {
await extensionPage.close();
Expand All @@ -117,9 +129,9 @@ test.describe("Issue 1591: Popup exclusion regression", () => {
const target = await context.newPage();
const extensionPage = await context.newPage();
try {
await target.goto("https://example.com/", { waitUntil: "domcontentloaded" });
await target.goto(targetUrl, { waitUntil: "domcontentloaded" });
await extensionPage.goto(`chrome-extension://${extensionId}/src/options.html`);
const targetTab = await getTargetTab(extensionPage);
const targetTab = await getTargetTab(extensionPage, targetUrl);
const popupData = await extensionPage.evaluate(
({ tabId, url }) =>
chrome.runtime.sendMessage({ action: "serviceWorker/popup/getPopupData", data: { tabId, url } }),
Expand Down
5 changes: 5 additions & 0 deletions packages/chrome-extension-mock/extension.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
export default class Extension {
inIncognitoContext = false;

// 默认已授权访问 file://;需要未授权场景的测试自行 spyOn 覆写。
isAllowedFileSchemeAccess(): Promise<boolean> {
return Promise.resolve(true);
}
}
2 changes: 2 additions & 0 deletions packages/chrome-extension-mock/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Permissions from "./permissions";
import Extension from "./extension";
import MockUserScripts from "./user_scripts";
import Action from "./action";
import WebNavigation from "./web_navigation";

const chromeMock = {
tabs: new MockTab(),
Expand All @@ -26,6 +27,7 @@ const chromeMock = {
extension: new Extension(),
userScripts: new MockUserScripts(),
action: new Action(),
webNavigation: new WebNavigation(),
init() {
this.downloads.reset();
this.permissions.reset();
Expand Down
8 changes: 8 additions & 0 deletions packages/chrome-extension-mock/web_navigation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default class WebNavigation {
// 默认无框架资料;需要框架的测试自行 spyOn 覆写返回值。
getAllFrames(
_details: chrome.webNavigation.GetAllFrameDetails
): Promise<chrome.webNavigation.GetAllFrameResultDetails[] | null> {
return Promise.resolve([]);
}
}
2 changes: 2 additions & 0 deletions src/app/cache_key.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export const CACHE_KEY_IMPORT_FILE = "importFile:"; // importFile 导入文件
export const CACHE_KEY_TAB_SCRIPT = "tabScript:";
// 记录某 tab 最近一次 content script 报到的 origin,用于判定「本页扩展是否触及得到」
export const CACHE_KEY_TAB_LOADED = "tabLoaded:";
export const CACHE_KEY_SET_VALUE = "setValue:";
export const CACHE_KEY_PERMISSION = "permission:";
export const CACHE_KEY_SKILL_INSTALL = "skillInstall:"; // Skill ZIP 待安装数据缓存
6 changes: 3 additions & 3 deletions src/app/service/service_worker/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { type Resource } from "@App/app/repo/resource";
import { type Subscribe } from "@App/app/repo/subscribe";
import { type Logger } from "@App/app/repo/logger";
import { type Permission } from "@App/app/repo/permission";
import type { InstallSource, ScriptMenu, ScriptMenuItem, TBatchUpdateListAction } from "./types";
import type { InstallSource, ScriptMenu, ScriptMenuItem, TBatchUpdateListAction, TPopupPageStatus } from "./types";
import { Client } from "@Packages/message/client";
import type { MessageSend } from "@Packages/message/types";
import type PermissionVerify from "./permission_verify";
Expand Down Expand Up @@ -245,8 +245,8 @@ export type GetPopupDataReq = {
};

export type GetPopupDataRes = {
// 在黑名单
isBlacklist: boolean;
// 当前页状态:非 ok 时 scriptList 为空,由 Popup 说明原因
pageStatus: TPopupPageStatus;
scriptList: ScriptMenu[];
backScriptList: ScriptMenu[];
};
Expand Down
Loading
Loading