Files
2026-07-13 13:20:22 +08:00

183 lines
6.4 KiB
TypeScript

/**
* Extension Settings Tab — iframe rendering, content verification, keep-alive.
*
* Supplements specs/ext-settings-tabs.e2e.ts (tab discovery, position anchoring,
* basic navigation). This file focuses on:
* 1. Page-route entry and hash verification
* 2. Iframe renders meaningful content (not just existence)
* 3. Tab switch round-trip: switch away then back, content survives
*/
import type { Page } from '@playwright/test';
import { test, expect } from '../../../fixtures';
import { goToSettings, goToExtensionSettings, waitForSettle, settingsSiderItemById } from '../../../helpers';
const KNOWN_TAB_IDS = ['ext-e2e-full-extension-e2e-settings', 'ext-hello-world-hello-settings'] as const;
const IFRAME_SEL = 'iframe[title*="Extension settings"]';
async function countTabsPresent(page: Page): Promise<number[]> {
return Promise.all(KNOWN_TAB_IDS.map((id) => page.locator(settingsSiderItemById(id)).count()));
}
async function waitForAnyExtTab(page: Page, timeout = 10_000): Promise<string | null> {
try {
await expect
.poll(async () => (await countTabsPresent(page)).some((c) => c > 0), {
timeout,
message: 'Waiting for at least one extension settings tab',
})
.toBeTruthy();
} catch {
return null;
}
const counts = await countTabsPresent(page);
const idx = counts.findIndex((c) => c > 0);
return idx >= 0 ? KNOWN_TAB_IDS[idx] : null;
}
async function waitForIframeLoaded(page: Page, timeoutMs = 15_000): Promise<void> {
const iframe = page.locator(IFRAME_SEL);
await expect
.poll(async () => Number(await iframe.first().evaluate((el) => getComputedStyle(el).opacity)), {
timeout: timeoutMs,
})
.toBe(1);
}
test.describe('Extension: Page-Route Entry', () => {
test('page route sets correct hash', async ({ page }) => {
await goToSettings(page, 'gemini');
const tabId = await waitForAnyExtTab(page);
test.skip(!tabId, 'No extension tabs installed');
await goToExtensionSettings(page, tabId!);
await waitForSettle(page);
const hash = await page.evaluate(() => window.location.hash);
expect(hash).toContain(`/settings/ext/${tabId}`);
});
test('sider highlights the active extension tab', async ({ page }) => {
await goToSettings(page, 'gemini');
const tabId = await waitForAnyExtTab(page);
test.skip(!tabId, 'No extension tabs installed');
await goToExtensionSettings(page, tabId!);
await waitForSettle(page);
const siderItem = page.locator(settingsSiderItemById(tabId!));
await expect(siderItem).toBeVisible({ timeout: 5_000 });
const cls = await siderItem.evaluate((el) => el.className);
expect(cls).toMatch(/active|selected/i);
});
});
test.describe('Extension: Iframe Content Rendering', () => {
test('renders an iframe or webview with a valid src', async ({ page }) => {
await goToSettings(page, 'gemini');
const tabId = await waitForAnyExtTab(page);
test.skip(!tabId, 'No extension tabs installed');
await goToExtensionSettings(page, tabId!);
await waitForSettle(page);
const iframe = page.locator(IFRAME_SEL);
const webview = page.locator('webview');
expect((await iframe.count()) > 0 || (await webview.count()) > 0).toBeTruthy();
if ((await iframe.count()) > 0) {
const src = await iframe.first().getAttribute('src');
expect(src).toBeTruthy();
expect(src).toMatch(/^https?:\/\/|^file:/);
}
});
test('iframe becomes fully visible after load', async ({ page }) => {
await goToSettings(page, 'gemini');
const tabId = await waitForAnyExtTab(page);
test.skip(!tabId, 'No extension tabs installed');
await goToExtensionSettings(page, tabId!);
test.skip((await page.locator(IFRAME_SEL).count()) === 0, 'External webview tab');
await waitForIframeLoaded(page);
});
test('iframe has sandbox attributes for local tabs', async ({ page }) => {
await goToSettings(page, 'gemini');
const tabId = await waitForAnyExtTab(page);
test.skip(!tabId, 'No extension tabs installed');
await goToExtensionSettings(page, tabId!);
await waitForSettle(page);
const iframe = page.locator(IFRAME_SEL);
test.skip((await iframe.count()) === 0, 'No iframe found');
const sandbox = await iframe.first().getAttribute('sandbox');
expect(sandbox).toContain('allow-scripts');
});
});
test.describe('Extension: Tab Switch Round-Trip', () => {
test('switch to builtin tab and back preserves extension content', async ({ page }) => {
await goToSettings(page, 'gemini');
const tabId = await waitForAnyExtTab(page);
test.skip(!tabId, 'No extension tabs installed');
await goToExtensionSettings(page, tabId!);
await waitForSettle(page);
const iframe = page.locator(IFRAME_SEL);
const hasIframe = (await iframe.count()) > 0;
let initialSrc: string | null = null;
if (hasIframe) {
await waitForIframeLoaded(page);
initialSrc = await iframe.first().getAttribute('src');
}
await goToSettings(page, 'system');
await waitForSettle(page);
expect(await page.evaluate(() => window.location.hash)).toContain('/settings/system');
await goToExtensionSettings(page, tabId!);
await waitForSettle(page);
if (hasIframe) {
const returned = page.locator(IFRAME_SEL);
await expect(returned.first()).toBeVisible({ timeout: 10_000 });
expect(await returned.first().getAttribute('src')).toBe(initialSrc);
}
});
test('switch between two extension tabs loads each correctly', async ({ page }) => {
await goToSettings(page, 'gemini');
let ids: string[] = [];
try {
await expect
.poll(
async () => {
const counts = await countTabsPresent(page);
ids = KNOWN_TAB_IDS.filter((_, i) => counts[i] > 0);
return ids.length;
},
{ timeout: 10_000, message: 'Waiting for multiple extension tabs' }
)
.toBeGreaterThanOrEqual(2);
} catch {
/* not enough tabs in this environment */
}
test.skip(ids.length < 2, 'Need at least 2 extension tabs installed');
await goToExtensionSettings(page, ids[0]);
await waitForSettle(page);
expect(await page.evaluate(() => window.location.hash)).toContain(`/settings/ext/${ids[0]}`);
await goToExtensionSettings(page, ids[1]);
await waitForSettle(page);
expect(await page.evaluate(() => window.location.hash)).toContain(`/settings/ext/${ids[1]}`);
});
});