opengeos--geolibre
7df9ebf22c
Sync labels / sync (push) Failing after 1m9s
Publish Container Image / Build and publish container image (push) Has been cancelled
Deploy Website / Deploy to GitHub Pages (push) Has been cancelled
Deploy Website / Build website and demo (push) Has been cancelled
Deploy viewer / Deploy viewer proxy to Cloudflare Workers (push) Has been cancelled
Deploy tiles / Deploy planetary tile proxy to Cloudflare Workers (push) Has been cancelled
Deploy collab / Deploy collaboration relay to Cloudflare Workers (push) Has been cancelled
CI / Dependency audit (push) Has been cancelled
CI / E2E smoke (Playwright) (push) Has been cancelled
CI / Validate CITATION.cff (push) Has been cancelled
CI / Build and test (push) Has been cancelled
114 行
4.3 KiB
TypeScript
114 行
4.3 KiB
TypeScript
import { expect, test, type Page } from "@playwright/test";
|
|
import { strToU8, zipSync } from "fflate";
|
|
|
|
// A minimal but valid external GeoLibre plugin: the entry is a self-contained
|
|
// ESM module exporting a GeoLibrePlugin whose id/name/version match the
|
|
// manifest. activate/deactivate are no-ops so the plugin registers without
|
|
// needing a live map control.
|
|
const PLUGIN_ID = "e2e-sample-plugin";
|
|
const PLUGIN_NAME = "E2E Sample Plugin";
|
|
const PLUGIN_VERSION = "1.0.0";
|
|
|
|
const MANIFEST = {
|
|
id: PLUGIN_ID,
|
|
name: PLUGIN_NAME,
|
|
version: PLUGIN_VERSION,
|
|
entry: "plugin.js",
|
|
};
|
|
|
|
const ENTRY_SOURCE = `const plugin = {
|
|
id: ${JSON.stringify(PLUGIN_ID)},
|
|
name: ${JSON.stringify(PLUGIN_NAME)},
|
|
version: ${JSON.stringify(PLUGIN_VERSION)},
|
|
activate() {},
|
|
deactivate() {},
|
|
};
|
|
export default plugin;
|
|
`;
|
|
|
|
// Wrap the plugin in a top-level folder (as produced by zipping a plugin
|
|
// directory) so this exercises the wrapping-folder path, not just a root
|
|
// plugin.json.
|
|
function buildPluginZip(): Buffer {
|
|
const archive = zipSync({
|
|
"e2e-sample-plugin/plugin.json": strToU8(JSON.stringify(MANIFEST)),
|
|
"e2e-sample-plugin/plugin.js": strToU8(ENTRY_SOURCE),
|
|
});
|
|
return Buffer.from(archive);
|
|
}
|
|
|
|
/** Open Manage Plugins from the Settings dropdown and switch to its Settings tab. */
|
|
async function openManagePluginsSettings(page: Page) {
|
|
await page.getByRole("button", { name: "Settings", exact: true }).click();
|
|
await page.getByRole("menuitem", { name: "Manage Plugins" }).click();
|
|
const dialog = page.getByRole("dialog");
|
|
await expect(dialog.getByText("Manage Plugins")).toBeVisible();
|
|
await dialog.getByRole("button", { name: "Settings", exact: true }).click();
|
|
return dialog;
|
|
}
|
|
|
|
test("installs a plugin from an uploaded zip, persists it across reload, and uninstalls it", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/");
|
|
await expect(page.getByTestId("map-canvas")).toBeVisible();
|
|
// The Settings dropdown trigger is part of the toolbar; wait for it before
|
|
// driving the menu.
|
|
await expect(
|
|
page.getByRole("button", { name: "Settings", exact: true }),
|
|
).toBeVisible();
|
|
|
|
// 1. Install from file: the web path reads the uploaded bytes, unpacks and
|
|
// registers the plugin client-side, and persists it in IndexedDB.
|
|
let dialog = await openManagePluginsSettings(page);
|
|
const [fileChooser] = await Promise.all([
|
|
page.waitForEvent("filechooser"),
|
|
dialog.getByRole("button", { name: /Choose \.zip/ }).click(),
|
|
]);
|
|
await fileChooser.setFiles({
|
|
name: `${PLUGIN_ID}.zip`,
|
|
mimeType: "application/zip",
|
|
buffer: buildPluginZip(),
|
|
});
|
|
|
|
// Success notice + the plugin appears in the "installed from file" list.
|
|
await expect(
|
|
dialog.getByText(`Installed plugin "${PLUGIN_ID}".`),
|
|
).toBeVisible();
|
|
await expect(dialog.getByText(PLUGIN_NAME)).toBeVisible();
|
|
await expect(
|
|
dialog.getByRole("button", { name: `Uninstall ${PLUGIN_NAME}` }),
|
|
).toBeVisible();
|
|
|
|
// Close the dialog and confirm the plugin registered (it shows in the
|
|
// toolbar's Plugins menu, which lists registered plugins by name).
|
|
await page.keyboard.press("Escape");
|
|
await page.getByRole("button", { name: "Plugins", exact: true }).click();
|
|
await expect(page.getByRole("menu").getByText(PLUGIN_NAME)).toBeVisible();
|
|
await page.keyboard.press("Escape");
|
|
|
|
// 2. Persistence: reload the app. The startup loader replays the bundle from
|
|
// IndexedDB, so the plugin is registered again and still listed.
|
|
await page.reload();
|
|
await expect(page.getByTestId("map-canvas")).toBeVisible();
|
|
await page.getByRole("button", { name: "Plugins", exact: true }).click();
|
|
await expect(page.getByRole("menu").getByText(PLUGIN_NAME)).toBeVisible();
|
|
await page.keyboard.press("Escape");
|
|
|
|
dialog = await openManagePluginsSettings(page);
|
|
await expect(dialog.getByText(PLUGIN_NAME)).toBeVisible();
|
|
|
|
// 3. Uninstall: removes it from IndexedDB and unregisters it.
|
|
await dialog
|
|
.getByRole("button", { name: `Uninstall ${PLUGIN_NAME}` })
|
|
.click();
|
|
await expect(
|
|
dialog.getByRole("button", { name: `Uninstall ${PLUGIN_NAME}` }),
|
|
).toHaveCount(0);
|
|
|
|
// It no longer appears in the Plugins menu either.
|
|
await page.keyboard.press("Escape");
|
|
await page.getByRole("button", { name: "Plugins", exact: true }).click();
|
|
await expect(page.getByRole("menu").getByText(PLUGIN_NAME)).toHaveCount(0);
|
|
});
|