From 6c31add0415d9a740c47a884c630d78541db1f77 Mon Sep 17 00:00:00 2001 From: HarelM Date: Thu, 9 Jul 2026 15:22:41 +0300 Subject: [PATCH] Reduce export surface --- e2e/maputnik-driver.ts | 19 ++++++------------- e2e/playwright-helper.ts | 36 +++++++++++++++++++++++------------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/e2e/maputnik-driver.ts b/e2e/maputnik-driver.ts index 4a16430e..57739833 100644 --- a/e2e/maputnik-driver.ts +++ b/e2e/maputnik-driver.ts @@ -1,11 +1,4 @@ -import { - Assertable, - PlaywrightHelper, - Query, - assertDeepNestedInclude, - readFixture, - retry, -} from "./playwright-helper"; +import { Assertable, PlaywrightHelper } from "./playwright-helper"; import { ModalDriver } from "./modal-driver"; const baseUrl = "http://localhost:8888/"; @@ -22,9 +15,9 @@ export class MaputnikAssertable extends Assertable { */ shouldEqualToStoredStyle = async () => { const expected = await (this.target as any); - await retry(async () => { + await this.retry(async () => { const stored = await this.getStoredStyle(); - assertDeepNestedInclude(expected, stored); + this.assertDeepNestedInclude(expected, stored); }); }; } @@ -202,14 +195,14 @@ export class MaputnikDriver { skipTargetLayerEditor: () => this.helper.get.elementByTestId("skip-target-layer-editor"), - styleFromLocalStorage: () => new Query(() => this.readStoredStyle()), + styleFromLocalStorage: () => this.helper.query(() => this.readStoredStyle()), - fixture: (name: string) => Promise.resolve(readFixture(name)), + fixture: (name: string) => Promise.resolve(this.helper.readFixture(name)), responseBody: (alias: string) => { // Our mocked style responses always return the matching fixture. const name = alias.endsWith(".json") ? alias : `${alias}.json`; - return Promise.resolve(readFixture(name)); + return Promise.resolve(this.helper.readFixture(name)); }, exampleFileUrl: () => baseUrl + "example-style.json", diff --git a/e2e/playwright-helper.ts b/e2e/playwright-helper.ts index 3e9a9c26..c2dcd310 100644 --- a/e2e/playwright-helper.ts +++ b/e2e/playwright-helper.ts @@ -8,17 +8,12 @@ const DATA_ATTRIBUTE = "data-wd-key"; const FIXTURES_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), "fixtures"); const isMac = process.platform === "darwin"; -export function testIdSelector(testId: string): string { +function testIdSelector(testId: string): string { return `[${DATA_ATTRIBUTE}="${testId}"]`; } -export function readFixture(name: string): any { - const contents = fs.readFileSync(path.join(FIXTURES_DIR, name), "utf-8"); - return JSON.parse(contents); -} - /** Retries `assertion` until it stops throwing */ -export async function retry( +async function retry( assertion: () => Promise | void, timeout = 10000, interval = 100 @@ -41,7 +36,7 @@ export async function retry( * A lazily-evaluated value (e.g. the style in localStorage). Assertions on a * Query re-read the value until they pass. */ -export class Query { +class Query { readonly __query = true as const; constructor(private readonly getter: () => Promise) {} @@ -68,7 +63,7 @@ function isLocator(target: unknown): target is Locator { } /** Asserts that every top-level key in `expected` deep-equals its counterpart in `actual`. */ -export function assertDeepNestedInclude(actual: any, expected: Record): void { +function assertDeepNestedInclude(actual: any, expected: Record): void { for (const key of Object.keys(expected)) { expect(actual?.[key], `property "${key}"`).toEqual(expected[key]); } @@ -82,6 +77,11 @@ export function assertDeepNestedInclude(actual: any, expected: Record { constructor(protected readonly target: T) {} + // Exposed to subclasses (e.g. MaputnikAssertable) so custom assertions can be + // built without re-importing these module-private helpers. + protected readonly retry = retry; + protected readonly assertDeepNestedInclude = assertDeepNestedInclude; + private locator(): Locator { if (!isLocator(this.target)) throw new Error("Expected a Locator target for this assertion"); return this.target; @@ -139,7 +139,7 @@ export class Assertable { this.assertValue((actual) => assertDeepNestedInclude(actual, value)); } -export async function typeSequence(page: Page, text: string): Promise { +async function typeSequence(page: Page, text: string): Promise { const tokens = text.match(/\{[^}]+\}|[^{]+/g) ?? []; const modifierMap: Record = { meta: "Meta", ctrl: "Control", shift: "Shift", alt: "Alt" }; const namedKeys: Record = { @@ -197,6 +197,16 @@ export class PlaywrightHelper { return this.page.locator(testIdSelector(testId)); } + /** Reads and parses a JSON fixture from the fixtures directory. */ + public readFixture(name: string): any { + return JSON.parse(fs.readFileSync(path.join(FIXTURES_DIR, name), "utf-8")); + } + + /** Wraps a lazily-evaluated value so assertions on it auto-retry. */ + public query(getter: () => Promise): Query { + return new Query(getter); + } + public given = { intercept: async (pattern: RegExp, alias: string, _method = "GET") => { this.recordedRequests.set(alias, []); @@ -218,7 +228,7 @@ export class PlaywrightHelper { if (alias) this.recordedRequests.get(alias)!.push(route.request()); const body = response && typeof response === "object" && "fixture" in (response as any) - ? readFixture((response as { fixture: string }).fixture) + ? this.readFixture((response as { fixture: string }).fixture) : response; route.fulfill({ json: body }); }); @@ -326,7 +336,7 @@ export class PlaywrightHelper { }, openFileByFixture: async (fixture: string, buttonTestId: string, inputTestId: string) => { - const content = JSON.stringify(readFixture(fixture)); + const content = JSON.stringify(this.readFixture(fixture)); const hasPicker = await this.page.evaluate(() => "showOpenFilePicker" in window); if (hasPicker) { await this.page.evaluate((fileContent) => { @@ -345,7 +355,7 @@ export class PlaywrightHelper { }, dropFileByFixture: async (fixture: string, dropzoneTestId: string) => { - const content = JSON.stringify(readFixture(fixture)); + const content = JSON.stringify(this.readFixture(fixture)); const dataTransfer = await this.page.evaluateHandle((fileContent) => { const dt = new DataTransfer(); dt.items.add(new File([fileContent], "example-style.json", { type: "application/json" }));