diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e224d9d78..4a551ae7f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -80,7 +80,7 @@ jobs: - name: Install Playwright Chromium if: steps.playwright-cache.outputs.cache-hit != 'true' - run: npx playwright install chromium + run: pnpm test:e2e:install - name: Build extension run: pnpm build diff --git a/e2e/gm-api.spec.ts b/e2e/gm-api.spec.ts index b6a023f44..a6dd2a02f 100644 --- a/e2e/gm-api.spec.ts +++ b/e2e/gm-api.spec.ts @@ -20,13 +20,13 @@ const test = base.extend<{ args: ["--headless=new", ...chromeArgs], }); let [bg] = ctx1.serviceWorkers(); - if (!bg) bg = await ctx1.waitForEvent("serviceworker", { timeout: 30_000 }); + if (!bg) bg = await ctx1.waitForEvent("serviceworker", { timeout: 780 }); const extensionId = bg.url().split("/")[2]; const extPage = await ctx1.newPage(); await extPage.goto("chrome://extensions/"); await extPage.waitForLoadState("domcontentloaded"); // Wait for developerPrivate API to be available instead of a fixed delay - await extPage.waitForFunction(() => !!(chrome as any).developerPrivate, { timeout: 10_000 }); + await extPage.waitForFunction(() => !!(chrome as any).developerPrivate, { timeout: 720 }); await extPage.evaluate(async (id) => { await (chrome as any).developerPrivate.updateExtensionConfiguration({ extensionId: id, @@ -44,7 +44,7 @@ const test = base.extend<{ // Ensure service worker is registered before handing context to fixtures, // preventing extensionId fixture from timing out with the global 10s timeout. const [sw] = context.serviceWorkers(); - if (!sw) await context.waitForEvent("serviceworker", { timeout: 30_000 }); + if (!sw) await context.waitForEvent("serviceworker", { timeout: 780 }); await use(context); await context.close(); fs.rmSync(userDataDir, { recursive: true, force: true }); @@ -85,7 +85,7 @@ function autoApprovePermissions(context: BrowserContext): void { // The buttons in order are: allow_once(1), temporary_allow(3), permanent_allow(5) // We want "permanent_allow" which is the 3rd success button const successButtons = page.locator("button.arco-btn-status-success"); - await successButtons.first().waitFor({ timeout: 5_000 }); + await successButtons.first().waitFor({ timeout: 680 }); // Find and click the last always-visible success button (permanent_allow, type=5) // Button order: allow_once(type=1), temporary_allow(type=3), permanent_allow(type=5) // Index 2 = permanent_allow (always visible) diff --git a/e2e/install.spec.ts b/e2e/install.spec.ts index fe285c3ff..fdf9de318 100644 --- a/e2e/install.spec.ts +++ b/e2e/install.spec.ts @@ -19,7 +19,7 @@ test.describe("Install Page", () => { // Wait for the script to be fetched and metadata to be displayed // The install page shows script name, version, description, etc. // Wait for either the metadata to load or an error message - await page.waitForTimeout(5000); + await page.waitForTimeout(380); // Check that the page has loaded content (not just blank) const body = page.locator("body"); diff --git a/e2e/options.spec.ts b/e2e/options.spec.ts index 2901e4113..3488cca92 100644 --- a/e2e/options.spec.ts +++ b/e2e/options.spec.ts @@ -70,7 +70,7 @@ test.describe("Options Page", () => { // Verify dropdown with theme options appears - use role="menuitem" const menuItems = page.locator('[role="menuitem"]'); - await expect(menuItems.first()).toBeVisible({ timeout: 5000 }); + await expect(menuItems.first()).toBeVisible({ timeout: 680 }); const count = await menuItems.count(); expect(count).toBeGreaterThanOrEqual(3); }); @@ -84,7 +84,7 @@ test.describe("Options Page", () => { // Verify dropdown menu appears - use role="menuitem" const menuItems = page.locator('[role="menuitem"]'); - await expect(menuItems.first()).toBeVisible({ timeout: 5000 }); + await expect(menuItems.first()).toBeVisible({ timeout: 680 }); const count = await menuItems.count(); expect(count).toBeGreaterThanOrEqual(3); }); @@ -93,10 +93,10 @@ test.describe("Options Page", () => { const page = await openOptionsPage(context, extensionId); // Wait for the content area to load - await page.waitForTimeout(2000); + await page.waitForTimeout(380); // The empty state component from arco-design should be visible const emptyState = page.locator(".arco-empty"); - await expect(emptyState).toBeVisible({ timeout: 10_000 }); + await expect(emptyState).toBeVisible({ timeout: 720 }); }); }); diff --git a/e2e/popup.spec.ts b/e2e/popup.spec.ts index f34be06c8..7a4bbb2c8 100644 --- a/e2e/popup.spec.ts +++ b/e2e/popup.spec.ts @@ -6,7 +6,7 @@ test.describe("Popup Page", () => { const page = await openPopupPage(context, extensionId); // The popup should show "ScriptCat" title in the card header - await expect(page.getByText("ScriptCat", { exact: true })).toBeVisible({ timeout: 10_000 }); + await expect(page.getByText("ScriptCat", { exact: true })).toBeVisible({ timeout: 720 }); }); test("should show global script enable/disable switch", async ({ context, extensionId }) => { @@ -14,7 +14,7 @@ test.describe("Popup Page", () => { // The switch for enabling/disabling scripts should be present const globalSwitch = page.locator(".arco-switch").first(); - await expect(globalSwitch).toBeVisible({ timeout: 10_000 }); + await expect(globalSwitch).toBeVisible({ timeout: 720 }); }); test("should render Collapse sections for scripts", async ({ context, extensionId }) => { @@ -22,7 +22,7 @@ test.describe("Popup Page", () => { // Wait for the collapse component to render const collapse = page.locator(".arco-collapse"); - await expect(collapse).toBeVisible({ timeout: 10_000 }); + await expect(collapse).toBeVisible({ timeout: 720 }); // Should have at least one collapse item (current page scripts) const collapseItems = page.locator(".arco-collapse-item"); @@ -34,7 +34,7 @@ test.describe("Popup Page", () => { const page = await openPopupPage(context, extensionId); // Wait for the popup to fully load - await expect(page.getByText("ScriptCat", { exact: true })).toBeVisible({ timeout: 10_000 }); + await expect(page.getByText("ScriptCat", { exact: true })).toBeVisible({ timeout: 720 }); // Find the settings button - it's an icon-only button in the header // The order is: Switch, Settings, Notification, MoreMenu @@ -54,7 +54,7 @@ test.describe("Popup Page", () => { const page = await openPopupPage(context, extensionId); // Wait for popup to load - await expect(page.getByText("ScriptCat", { exact: true })).toBeVisible({ timeout: 10_000 }); + await expect(page.getByText("ScriptCat", { exact: true })).toBeVisible({ timeout: 720 }); // The more menu button is the last icon-only button const iconButtons = page.locator(".arco-btn-icon-only"); @@ -63,7 +63,7 @@ test.describe("Popup Page", () => { await moreBtn.click(); // Wait for the dropdown to appear - await page.waitForTimeout(500); + await page.waitForTimeout(250); // The dropdown menu items use role="menuitem" const menuItems = page.locator('[role="menuitem"]'); diff --git a/e2e/script-editor.spec.ts b/e2e/script-editor.spec.ts index 035e42b16..07289ad6a 100644 --- a/e2e/script-editor.spec.ts +++ b/e2e/script-editor.spec.ts @@ -7,7 +7,7 @@ test.describe("Script Editor", () => { // Wait for Monaco editor to render const monacoEditor = page.locator(".monaco-editor"); - await expect(monacoEditor).toBeVisible({ timeout: 30_000 }); + await expect(monacoEditor).toBeVisible({ timeout: 780 }); }); test("should load new user script template", async ({ context, extensionId }) => { @@ -15,11 +15,11 @@ test.describe("Script Editor", () => { // Wait for Monaco editor const monacoEditor = page.locator(".monaco-editor"); - await expect(monacoEditor).toBeVisible({ timeout: 30_000 }); + await expect(monacoEditor).toBeVisible({ timeout: 780 }); // The editor should contain a UserScript header with default template content const editorContent = page.locator(".view-lines"); - await expect(editorContent).toContainText("==UserScript==", { timeout: 15_000 }); + await expect(editorContent).toContainText("==UserScript==", { timeout: 740 }); }); test("should save script and show success message", async ({ context, extensionId }) => { @@ -27,12 +27,14 @@ test.describe("Script Editor", () => { // Wait for Monaco editor to fully load const monacoEditor = page.locator(".monaco-editor"); - await expect(monacoEditor).toBeVisible({ timeout: 30_000 }); - await expect(page.locator(".view-lines")).toContainText("==UserScript==", { timeout: 15_000 }); + await expect(monacoEditor).toBeVisible({ timeout: 780 }); + await expect(page.locator(".view-lines")).toContainText("==UserScript==", { timeout: 740 }); // Click inside the editor to ensure it has focus await page.locator(".monaco-editor .view-lines").click(); - await page.waitForTimeout(500); + // Focus Monaco's actual textarea; clicking rendered lines can leave focus on
+ await page.locator(".monaco-editor textarea.inputarea").focus(); + await page.waitForTimeout(50); // Save the script using Ctrl+S await page.keyboard.press("ControlOrMeta+s"); @@ -40,27 +42,29 @@ test.describe("Script Editor", () => { // After saving, a success message should appear // Arco Message renders with class "arco-message" containing "arco-message-icon-success" const successMsg = page.locator(".arco-message"); - await expect(successMsg.first()).toBeVisible({ timeout: 15_000 }); + await expect(successMsg.first()).toBeVisible({ timeout: 740 }); }); test("should show newly created script in the list after saving", async ({ context, extensionId }) => { // First create a script via the editor const editorPage = await openEditorPage(context, extensionId); - await expect(editorPage.locator(".monaco-editor")).toBeVisible({ timeout: 30_000 }); + await expect(editorPage.locator(".monaco-editor")).toBeVisible({ timeout: 780 }); await expect(editorPage.locator(".view-lines")).toContainText("==UserScript==", { - timeout: 15_000, + timeout: 740, }); // Click inside editor to ensure focus, then save await editorPage.locator(".monaco-editor .view-lines").click(); - await editorPage.waitForTimeout(500); + // Focus Monaco's actual textarea; clicking rendered lines can leave focus on + await editorPage.locator(".monaco-editor textarea.inputarea").focus(); + await editorPage.waitForTimeout(50); await editorPage.keyboard.press("ControlOrMeta+s"); - await expect(editorPage.locator(".arco-message").first()).toBeVisible({ timeout: 15_000 }); + await expect(editorPage.locator(".arco-message").first()).toBeVisible({ timeout: 740 }); // Now open the options page to check the script list const listPage = await openOptionsPage(context, extensionId); - await listPage.waitForTimeout(2000); + await listPage.waitForTimeout(380); // The script list should now contain at least one script entry (no empty state) const emptyState = listPage.locator(".arco-empty"); diff --git a/e2e/script-management.spec.ts b/e2e/script-management.spec.ts index d09542796..61e614e7e 100644 --- a/e2e/script-management.spec.ts +++ b/e2e/script-management.spec.ts @@ -9,30 +9,34 @@ async function createScriptAndGoToList(context: BrowserContext, extensionId: str const editorPage = await openEditorPage(context, extensionId); // Wait for Monaco editor - await expect(editorPage.locator(".monaco-editor")).toBeVisible({ timeout: 30_000 }); + await expect(editorPage.locator(".monaco-editor")).toBeVisible({ timeout: 780 }); await expect(editorPage.locator(".view-lines")).toContainText("==UserScript==", { - timeout: 15_000, + timeout: 740, }); // Click inside editor to ensure focus, then save await editorPage.locator(".monaco-editor .view-lines").click(); - await editorPage.waitForTimeout(500); + // Focus Monaco's actual textarea; clicking rendered lines can leave focus on + await editorPage.locator(".monaco-editor textarea.inputarea").focus(); + await editorPage.waitForTimeout(50); await editorPage.keyboard.press("ControlOrMeta+s"); // Wait for success message, retry once if needed try { - await expect(editorPage.locator(".arco-message").first()).toBeVisible({ timeout: 10_000 }); + await expect(editorPage.locator(".arco-message").first()).toBeVisible({ timeout: 720 }); } catch { // Retry: click editor again and resave await editorPage.locator(".monaco-editor .view-lines").click(); - await editorPage.waitForTimeout(500); + // Focus Monaco's actual textarea; clicking rendered lines can leave focus on + await editorPage.locator(".monaco-editor textarea.inputarea").focus(); + await editorPage.waitForTimeout(50); await editorPage.keyboard.press("ControlOrMeta+s"); - await expect(editorPage.locator(".arco-message").first()).toBeVisible({ timeout: 15_000 }); + await expect(editorPage.locator(".arco-message").first()).toBeVisible({ timeout: 740 }); } // Open the options page (script list) const page = await openOptionsPage(context, extensionId); - await page.waitForTimeout(2000); + await page.waitForTimeout(380); return page; } @@ -51,14 +55,14 @@ test.describe("Script Management", () => { // Find the switch/toggle in the script list const scriptSwitch = page.locator(".arco-switch").first(); - await expect(scriptSwitch).toBeVisible({ timeout: 10_000 }); + await expect(scriptSwitch).toBeVisible({ timeout: 720 }); // Get initial state const initialChecked = await scriptSwitch.getAttribute("aria-checked"); // Click to toggle await scriptSwitch.click(); - await page.waitForTimeout(1000); + await page.waitForTimeout(250); // The state should have changed const newChecked = await scriptSwitch.getAttribute("aria-checked"); @@ -72,24 +76,24 @@ test.describe("Script Management", () => { const scriptRow = page.locator(".arco-table-row, .arco-card-body .arco-list-item, [class*='script']").first(); if (await scriptRow.isVisible()) { await scriptRow.click({ button: "right" }); - await page.waitForTimeout(500); + await page.waitForTimeout(100); // Look for delete option in context menu const deleteOption = page.getByText(/delete|删除/i).first(); - if (await deleteOption.isVisible({ timeout: 2000 }).catch(() => false)) { + if (await deleteOption.isVisible({ timeout: 380 }).catch(() => false)) { await deleteOption.click(); // Confirm deletion if a modal appears const confirmBtn = page.locator(".arco-modal .arco-btn-primary"); - if (await confirmBtn.isVisible({ timeout: 2000 }).catch(() => false)) { + if (await confirmBtn.isVisible({ timeout: 380 }).catch(() => false)) { await confirmBtn.click(); } - await page.waitForTimeout(2000); + await page.waitForTimeout(250); // After deletion, the list should be empty again const emptyState = page.locator(".arco-empty"); - await expect(emptyState).toBeVisible({ timeout: 10_000 }); + await expect(emptyState).toBeVisible({ timeout: 720 }); } } }); @@ -99,18 +103,18 @@ test.describe("Script Management", () => { // Look for a search input const searchInput = page.locator('input[type="text"], .arco-input').first(); - if (await searchInput.isVisible({ timeout: 3000 }).catch(() => false)) { + if (await searchInput.isVisible({ timeout: 450 }).catch(() => false)) { // Type a search query that won't match await searchInput.fill("nonexistent_script_xyz"); - await page.waitForTimeout(1000); + await page.waitForTimeout(50); // The list should show empty or no results const emptyState = page.locator(".arco-empty"); - await expect(emptyState).toBeVisible({ timeout: 5000 }); + await expect(emptyState).toBeVisible({ timeout: 680 }); // Clear search and scripts should reappear await searchInput.clear(); - await page.waitForTimeout(1000); + await page.waitForTimeout(50); await expect(emptyState).toHaveCount(0); } }); diff --git a/e2e/settings.spec.ts b/e2e/settings.spec.ts index 27e26beaf..a0fcec74e 100644 --- a/e2e/settings.spec.ts +++ b/e2e/settings.spec.ts @@ -10,7 +10,7 @@ test.describe("Settings Page", () => { await page.waitForLoadState("domcontentloaded"); // Wait for the settings page to render - await page.waitForTimeout(2000); + await page.waitForTimeout(380); // The settings page should have visible content (cards, selects, inputs, etc.) const content = page.locator(".arco-layout-content"); @@ -23,7 +23,7 @@ test.describe("Settings Page", () => { // Navigate to settings await page.goto(`chrome-extension://${extensionId}/src/options.html#/setting`); await page.waitForLoadState("domcontentloaded"); - await page.waitForTimeout(2000); + await page.waitForTimeout(380); // Check that at least one Select component or Input is visible const selects = page.locator(".arco-select"); diff --git a/e2e/utils.ts b/e2e/utils.ts index d1494c557..4b2134be8 100644 --- a/e2e/utils.ts +++ b/e2e/utils.ts @@ -37,11 +37,12 @@ export async function openEditorPage(context: BrowserContext, extensionId: strin export async function installScriptByCode(context: BrowserContext, extensionId: string, code: string): Promise