From 0eaf131dd6ab9500c5ea4a940e3f50c413a521e0 Mon Sep 17 00:00:00 2001 From: Vladimir Lazar <106525396+cbr7@users.noreply.github.com> Date: Tue, 14 Apr 2026 13:31:41 +0200 Subject: [PATCH] chore(test): refactoring of runner factory class (#17045) * chore(test): refactoring of runner factory class --- .gitignore | 1 + .../playwright/src/runner/electron-runner.ts | 5 +- tests/playwright/src/runner/runner-factory.ts | 68 ++++++++++++------- .../src/specs/navigation-history.spec.ts | 28 +++++--- tests/playwright/src/utility/fixtures.ts | 2 +- 5 files changed, 63 insertions(+), 41 deletions(-) diff --git a/.gitignore b/.gitignore index 0c6e0e1af76..d1adb4ef091 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ yarn.lock extensions/podman/assets/ extensions-extra/ __mocks__/@podman-desktop/api.ts +contexts/meta/ diff --git a/tests/playwright/src/runner/electron-runner.ts b/tests/playwright/src/runner/electron-runner.ts index d1dacd88785..bb8f010cbff 100644 --- a/tests/playwright/src/runner/electron-runner.ts +++ b/tests/playwright/src/runner/electron-runner.ts @@ -23,10 +23,9 @@ import type { ElectronApplication, JSHandle, Page } from '@playwright/test'; import { _electron as electron } from '@playwright/test'; import type { BrowserWindow } from 'electron'; +import { Runner } from '/@/runner/podman-desktop-runner'; import { RunnerFactory } from '/@/runner/runner-factory'; - -import { Runner } from './podman-desktop-runner'; -import type { RunnerOptions } from './runner-options'; +import type { RunnerOptions } from '/@/runner/runner-options'; type WindowState = { isVisible: boolean; diff --git a/tests/playwright/src/runner/runner-factory.ts b/tests/playwright/src/runner/runner-factory.ts index c65adc4f8fb..a92839147ed 100644 --- a/tests/playwright/src/runner/runner-factory.ts +++ b/tests/playwright/src/runner/runner-factory.ts @@ -15,42 +15,58 @@ * * SPDX-License-Identifier: Apache-2.0 ***********************************************************************/ +/** biome-ignore-all lint/complexity/noStaticOnlyClass: */ import { ChromeDevToolsProtocolRunner } from '/@/runner/chrome-dev-tools-protocol-runner'; import { ElectronRunner } from '/@/runner/electron-runner'; import type { Runner } from '/@/runner/podman-desktop-runner'; import { RunnerOptions } from '/@/runner/runner-options'; export class RunnerFactory { - protected static _instance: Runner | undefined; + private static _instance: Runner | undefined; + private static _initializing: Promise | undefined; - static async getInstance({ - runnerOptions = new RunnerOptions(), - }: { - runnerOptions?: RunnerOptions; - } = {}): Promise { - if (!this._instance) { - const pdArgs = process.env.PODMAN_DESKTOP_ARGS; - const pdBinary = process.env.PODMAN_DESKTOP_BINARY; - const debugPort = process.env.DEBUGGING_PORT; - if ( - (pdArgs && !pdBinary && !debugPort) || - (pdBinary && !pdArgs && !debugPort) || - (!pdArgs && !pdBinary && !debugPort) - ) { - this._instance = new ElectronRunner({ runnerOptions }); - } else if (pdBinary && debugPort) { - this._instance = new ChromeDevToolsProtocolRunner({ runnerOptions }); - } else { - throw new Error( - 'Allowed combinations are standalone PODMAN_DESKTOP_ARGS or PODMAN_DESKTOP_BINARY or neither of them for electron runner. Or PODMAN_DESKTOP_BINARY and DEBUGGING_PORT for CDP runner...', - ); - } - await this._instance.start(); + static async getInstance(runnerOptions?: RunnerOptions): Promise { + if (RunnerFactory._instance) return RunnerFactory._instance; + if (RunnerFactory._initializing) return RunnerFactory._initializing; + + RunnerFactory._initializing = RunnerFactory.create(runnerOptions ?? new RunnerOptions()); + try { + RunnerFactory._instance = await RunnerFactory._initializing; + return RunnerFactory._instance; + } finally { + RunnerFactory._initializing = undefined; } - return this._instance; + } + + private static async create(runnerOptions: RunnerOptions): Promise { + const runner = RunnerFactory.resolveRunner(runnerOptions); + await runner.start(); + return runner; + } + + private static resolveRunner(runnerOptions: RunnerOptions): Runner { + const pdArgs = process.env.PODMAN_DESKTOP_ARGS; + const pdBinary = process.env.PODMAN_DESKTOP_BINARY; + const debugPort = process.env.DEBUGGING_PORT; + + if (pdArgs && pdBinary) { + throw new Error('PODMAN_DESKTOP_ARGS and PODMAN_DESKTOP_BINARY are mutually exclusive'); + } + if (pdArgs && debugPort) { + throw new Error('DEBUGGING_PORT requires PODMAN_DESKTOP_BINARY, not PODMAN_DESKTOP_ARGS'); + } + if (debugPort && !pdBinary) { + throw new Error('DEBUGGING_PORT requires PODMAN_DESKTOP_BINARY to be set'); + } + + if (pdBinary && debugPort) { + return new ChromeDevToolsProtocolRunner({ runnerOptions }); + } + return new ElectronRunner({ runnerOptions }); } static dispose(): void { - this._instance = undefined; + RunnerFactory._instance = undefined; + RunnerFactory._initializing = undefined; } } diff --git a/tests/playwright/src/specs/navigation-history.spec.ts b/tests/playwright/src/specs/navigation-history.spec.ts index eed9acbd8f3..7d0a4b945c4 100644 --- a/tests/playwright/src/specs/navigation-history.spec.ts +++ b/tests/playwright/src/specs/navigation-history.spec.ts @@ -116,18 +116,24 @@ test.describe await playExpect(imagesPage.heading).toBeVisible({ timeout: 5_000 }); await navigationBar.goBack(); // Now on Containers with forward available await playExpect(containerPage.heading).toBeVisible({ timeout: 5_000 }); - // Simulate trackpad swipe left (deltaX > 30 for forward) - await page.evaluate(() => { - const event = new WheelEvent('wheel', { - deltaX: 50, // > 30 threshold for forward navigation - deltaY: 0, - bubbles: true, - }); - document.body.dispatchEvent(event); - }); - // Verify navigation to Images - await playExpect(imagesPage.heading).toBeVisible({ timeout: 5_000 }); + const forwardButton = page.getByRole('button', { name: 'Forward (hold for history)' }); + await playExpect(forwardButton).toBeEnabled({ timeout: 5_000 }); + + // The app has a 500ms swipe cooldown (NavigationButtons.handleWheel) that may + // still be active from the previous trackpad-swipe-left test. Poll the dispatch + // so the event is retried once the cooldown expires. + await playExpect + .poll( + async () => { + await page.evaluate(() => { + document.body.dispatchEvent(new WheelEvent('wheel', { deltaX: 50, deltaY: 0, bubbles: true })); + }); + return imagesPage.heading.isVisible(); + }, + { timeout: 5_000 }, + ) + .toBeTruthy(); }); test('Navigation shortcuts blocked when focus in input field', async ({ navigationBar, page }) => { diff --git a/tests/playwright/src/utility/fixtures.ts b/tests/playwright/src/utility/fixtures.ts index 95b85ff9be5..c65700b934d 100644 --- a/tests/playwright/src/utility/fixtures.ts +++ b/tests/playwright/src/utility/fixtures.ts @@ -41,7 +41,7 @@ export type FixtureOptions = { export const test = base.extend({ runnerOptions: [new RunnerOptions(), { option: true }], runner: async ({ runnerOptions }, use) => { - const runner = await RunnerFactory.getInstance({ runnerOptions }); + const runner = await RunnerFactory.getInstance(runnerOptions); await use(runner); }, page: async ({ runner }, use) => {