import { execFileSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; import { test as base, expect, type Page } from "@playwright/test"; // --------------------------------------------------------------------------- // login() — fill the login form and submit (for tests that need fresh login) // --------------------------------------------------------------------------- export async function login(page: Page, username = "admin", password = "admin") { await page.goto("/login"); await page.getByLabel("Username").fill(username); await page.getByLabel("Password").fill(password); await page.getByRole("button", { name: /login/i }).click(); await page.waitForURL("/", { timeout: 15_000 }); } // --------------------------------------------------------------------------- // createTestImageFile() — create a small test PNG on disk and return its path // --------------------------------------------------------------------------- let _testImagePath: string | null = null; export function getTestImagePath(): string { if (_testImagePath && fs.existsSync(_testImagePath)) return _testImagePath; const dir = path.join(process.cwd(), "test-results"); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); _testImagePath = path.join(dir, "test-image.png"); try { const script = [ "const sharp = require('sharp');", `sharp({create:{width:100,height:100,channels:4,background:{r:255,g:0,b:0,alpha:1}}}).png().toFile('${_testImagePath.replace(/'/g, "\\'")}')`, ].join(" "); execFileSync("node", ["-e", script], { cwd: process.cwd(), timeout: 5000, }); } catch { // Fallback: write a minimal 1x1 PNG manually const minimalPng = Buffer.from( "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwADhQGAWjR9awAAAABJRU5ErkJggg==", "base64", ); fs.writeFileSync(_testImagePath, minimalPng); } return _testImagePath; } // --------------------------------------------------------------------------- // uploadTestImage() — upload a test image via the file chooser on a tool page // --------------------------------------------------------------------------- export async function uploadTestImage(page: Page): Promise { const testImagePath = getTestImagePath(); const fileChooserPromise = page.waitForEvent("filechooser"); const dropzone = page.locator("[class*='border-dashed']").first(); await dropzone.click(); const fileChooser = await fileChooserPromise; await fileChooser.setFiles(testImagePath); // Wait for React state to update await page.waitForTimeout(500); } // --------------------------------------------------------------------------- // waitForProcessing() — wait for processing to complete // --------------------------------------------------------------------------- export async function waitForProcessing(page: Page, timeoutMs = 30_000) { try { const spinner = page.locator("[class*='animate-spin']"); if (await spinner.isVisible({ timeout: 2000 })) { await spinner.waitFor({ state: "hidden", timeout: timeoutMs }); } } catch { // No spinner appeared — processing may have been instant } } // --------------------------------------------------------------------------- // Custom test fixture — loggedInPage uses the saved storageState // (all "chromium" project tests already have auth via storageState, // but this provides backward compatibility for tests that use it) // --------------------------------------------------------------------------- export const test = base.extend<{ loggedInPage: Page }>({ loggedInPage: async ({ page }, use) => { // storageState is already loaded by the project config, just navigate await page.goto("/"); await use(page); }, }); export { expect };