Fix web push delivery handling and worker console
Some checks failed
docker-images / resolve-build-targets (push) Successful in 5s
docker-images / build-and-push (admin) (push) Successful in 30s
docker-images / submit-indexnow (push) Has been cancelled
docker-images / build-and-push (frontend) (push) Has been cancelled
docker-images / build-and-push (backend) (push) Has been cancelled

This commit is contained in:
2026-04-04 04:15:20 +08:00
parent ab18bbaf23
commit 381dc9b854
19 changed files with 1607 additions and 747 deletions

View File

@@ -0,0 +1,68 @@
import path from "node:path";
import { fileURLToPath } from "node:url";
import { defineConfig, devices } from "@playwright/test";
const backendBaseUrl = "http://127.0.0.1:5150";
const frontendBaseUrl = "http://127.0.0.1:4321";
const isCi = Boolean(process.env.CI);
const webPushChannel =
process.env.PLAYWRIGHT_WEB_PUSH_CHANNEL ??
(process.platform === "win32" ? "msedge" : undefined);
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const repoRoot = path.resolve(__dirname, "..");
export default defineConfig({
testDir: "./tests",
testMatch: /web-push\.real\.spec\.ts/,
fullyParallel: false,
workers: 1,
timeout: 180_000,
expect: {
timeout: 20_000,
},
reporter: [["list"]],
use: {
...devices["Desktop Chrome"],
channel: webPushChannel,
baseURL: frontendBaseUrl,
headless: true,
trace: "on-first-retry",
screenshot: "only-on-failure",
video: "retain-on-failure",
},
webServer: [
{
command: "cargo loco start --server-and-worker",
cwd: path.resolve(repoRoot, "backend"),
url: `${backendBaseUrl}/api/site_settings`,
reuseExistingServer: false,
stdout: "pipe",
stderr: "pipe",
env: {
...process.env,
DATABASE_URL:
process.env.DATABASE_URL ??
"postgres://postgres:postgres%402025%21@10.0.0.2:5432/termi-api_development",
REDIS_URL: process.env.REDIS_URL ?? "redis://127.0.0.1:6379",
TERMI_ADMIN_LOCAL_LOGIN_ENABLED:
process.env.TERMI_ADMIN_LOCAL_LOGIN_ENABLED ?? "true",
},
},
{
command: "pnpm dev --host 127.0.0.1 --port 4321",
cwd: path.resolve(repoRoot, "frontend"),
url: frontendBaseUrl,
reuseExistingServer: false,
stdout: "pipe",
stderr: "pipe",
env: {
...process.env,
PUBLIC_API_BASE_URL: `${backendBaseUrl}/api`,
INTERNAL_API_BASE_URL: `${backendBaseUrl}/api`,
PUBLIC_IMAGE_ALLOWED_HOSTS: "127.0.0.1,127.0.0.1:5150",
},
},
],
});

View File

@@ -0,0 +1,122 @@
import { chromium, expect, test } from "@playwright/test";
const BACKEND_BASE_URL = "http://127.0.0.1:5150";
const FRONTEND_BASE_URL = "http://127.0.0.1:4321";
type AdminSubscriptionRecord = {
id: number;
channel_type: string;
target: string;
};
type AdminSubscriptionListResponse = {
subscriptions: AdminSubscriptionRecord[];
};
declare global {
interface Window {
__termiPushMessages?: unknown[];
__termiSubscriptionPopupReady?: boolean;
}
}
test("浏览器订阅后可以收到测试推送", async ({ request }, testInfo) => {
const context = await chromium.launchPersistentContext(
testInfo.outputPath("web-push-user-data"),
{
headless: true,
viewport: { width: 1280, height: 800 },
},
);
try {
await context.grantPermissions(["notifications"], {
origin: FRONTEND_BASE_URL,
});
const page = context.pages()[0] ?? (await context.newPage());
await page.addInitScript(() => {
window.__termiPushMessages = [];
navigator.serviceWorker?.addEventListener("message", (event) => {
if (event.data?.type === "termi:web-push-received") {
window.__termiPushMessages?.push(event.data.payload ?? null);
}
});
});
await page.goto(`${FRONTEND_BASE_URL}/maintenance?returnTo=%2F`);
await page.getByLabel("访问口令").fill("termi");
await page.getByRole("button", { name: "进入站点" }).click();
await page.waitForURL(`${FRONTEND_BASE_URL}/`);
await page.waitForFunction(
() => window.__termiSubscriptionPopupReady === true,
);
await page.locator("[data-subscription-popup-open]").click();
const subscribeResponsePromise = page.waitForResponse(
(response) =>
response.url().includes("/api/subscriptions/combined") &&
response.request().method() === "POST",
);
await page.locator("[data-subscription-popup-submit]").click();
const subscribeResponse = await subscribeResponsePromise;
expect(subscribeResponse.ok()).toBeTruthy();
await expect(
page.locator('[data-subscription-popup-status][data-state="success"]'),
).toBeVisible();
const endpoint = await page.evaluate(async () => {
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.getSubscription();
return subscription?.endpoint ?? null;
});
expect(endpoint).toBeTruthy();
const loginResponse = await request.post(
`${BACKEND_BASE_URL}/api/admin/session/login`,
{
data: {
username: "admin",
password: "admin123",
},
},
);
expect(loginResponse.ok()).toBeTruthy();
const subscriptionsResponse = await request.get(
`${BACKEND_BASE_URL}/api/admin/subscriptions`,
);
expect(subscriptionsResponse.ok()).toBeTruthy();
const subscriptionsPayload =
(await subscriptionsResponse.json()) as AdminSubscriptionListResponse;
const subscription = subscriptionsPayload.subscriptions.find(
(item) => item.channel_type === "web_push" && item.target === endpoint,
);
expect(subscription).toBeTruthy();
const testResponse = await request.post(
`${BACKEND_BASE_URL}/api/admin/subscriptions/${subscription?.id}/test`,
);
expect(testResponse.ok()).toBeTruthy();
await page.waitForFunction(
() =>
Array.isArray(window.__termiPushMessages) &&
window.__termiPushMessages.length > 0,
undefined,
{ timeout: 45_000 },
);
const messages = await page.evaluate(() => window.__termiPushMessages ?? []);
expect(messages.length).toBeGreaterThan(0);
} finally {
await context.close();
}
});