UNPKG

playwright-cucumber-ts-steps

Version:

A collection of reusable Playwright step definitions for Cucumber in TypeScript, designed to streamline end-to-end testing across web, API, and mobile applications.

211 lines (210 loc) โ€ข 9.67 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const cucumber_1 = require("@cucumber/cucumber"); const dotenv = __importStar(require("dotenv")); const playwright_1 = require("playwright"); const compareSnapshots_1 = require("./compareSnapshots"); // Set to 30 seconds (0, cucumber_1.setDefaultTimeout)(30 * 1000); dotenv.config(); let sharedBrowser; (0, cucumber_1.BeforeAll)(async () => { sharedBrowser = await playwright_1.chromium.launch({ headless: process.env.HEADLESS !== "false", }); console.log("๐Ÿš€ Launched shared browser for all scenarios"); }); (0, cucumber_1.AfterAll)(async () => { await sharedBrowser?.close(); console.log("๐Ÿงน Closed shared browser after all scenarios"); }); (0, cucumber_1.Before)(async function (scenario) { const params = this.parameters || {}; const ARTIFACT_DIR = params.artifactDir || process.env.TEST_ARTIFACT_DIR || "test-artifacts"; const SCREENSHOT_DIR = path_1.default.resolve(ARTIFACT_DIR, "screenshots"); const VIDEO_DIR = path_1.default.resolve(ARTIFACT_DIR, "videos"); const TRACE_DIR = path_1.default.resolve(ARTIFACT_DIR, "traces"); const SESSION_FILE = path_1.default.resolve(ARTIFACT_DIR, "auth-cookies", "session.json"); this.data.artifactDir = ARTIFACT_DIR; this.data.screenshotDir = SCREENSHOT_DIR; this.data.videoDir = VIDEO_DIR; this.data.traceDir = TRACE_DIR; this.data.sessionFile = SESSION_FILE; // Modes: "false" | "fail" | "all" const traceMode = (params.enableTrace || process.env.ENABLE_TRACE || "false").toLowerCase(); const screenshotMode = (params.enableScreenshots || process.env.ENABLE_SCREENSHOTS || "false").toLowerCase(); const videoMode = (params.enableVideos || process.env.ENABLE_VIDEOS || "false").toLowerCase(); this.data.traceMode = traceMode; this.data.screenshotMode = screenshotMode; this.data.videoMode = videoMode; const isMobileTag = scenario.pickle.tags.some((t) => t.name === "@mobile"); const deviceName = params.device || process.env.MOBILE_DEVICE || (isMobileTag ? "iPhone 13 Pro" : null); const deviceSettings = deviceName ? playwright_1.devices[deviceName] : undefined; if (deviceName && !deviceSettings) { throw new Error(`๐Ÿšซ Invalid MOBILE_DEVICE: "${deviceName}" is not recognized by Playwright.`); } const isVisualTest = params.enableVisualTest ?? (process.env.ENABLE_VISUAL_TEST === "true" || scenario.pickle.tags.some((t) => t.name === "@visual")); this.data.enableVisualTest = isVisualTest; if (isVisualTest) process.env.VISUAL_TEST = "true"; const contextOptions = { ...(videoMode !== "false" ? { recordVideo: { dir: VIDEO_DIR } } : {}), ...(deviceSettings || {}), }; if (fs_1.default.existsSync(SESSION_FILE)) { contextOptions.storageState = SESSION_FILE; this.log?.("โœ… Reusing session from saved file."); } const context = await sharedBrowser.newContext(contextOptions); const page = await context.newPage(); this.browser = sharedBrowser; this.context = context; this.page = page; if (traceMode !== "false") { await context.tracing.start({ screenshots: true, snapshots: true, sources: true, }); this.data.tracingStarted = true; this.log?.(`๐Ÿงช Tracing started (${traceMode})`); } if (deviceName) this.log?.(`๐Ÿ“ฑ Mobile emulation enabled (${deviceName})`); }); (0, cucumber_1.After)(async function (scenario) { const name = scenario.pickle.name.replace(/[^a-z0-9]+/gi, "_").toLowerCase(); const failed = scenario.result?.status === "FAILED"; const mode = (value) => value?.toLowerCase(); const screenshotMode = mode(this.parameters?.enableScreenshots || process.env.ENABLE_SCREENSHOTS); const videoMode = mode(this.parameters?.enableVideos || process.env.ENABLE_VIDEOS); const traceMode = mode(this.parameters?.enableTrace || process.env.ENABLE_TRACE); const shouldSaveScreenshot = screenshotMode === "all" || (screenshotMode === "fail" && failed); const shouldSaveVideo = videoMode === "all" || (videoMode === "fail" && failed); const shouldSaveTrace = traceMode === "all" || (traceMode === "fail" && failed); // ๐Ÿ“ธ Screenshot if (shouldSaveScreenshot && this.page) { const screenshotPath = path_1.default.join(this.data.screenshotDir, `${failed ? "failed-" : ""}${name}.png`); try { fs_1.default.mkdirSync(this.data.screenshotDir, { recursive: true }); await this.page.screenshot({ path: screenshotPath, fullPage: true }); console.log(`๐Ÿ–ผ๏ธ Screenshot saved: ${screenshotPath}`); } catch (err) { console.warn("โŒ Failed to save screenshot:", err); } } // ๐ŸŽฅ Video if (this.page && videoMode !== "false") { try { const video = this.page.video(); if (video) { const rawPath = await video.path(); if (fs_1.default.existsSync(rawPath)) { const finalPath = path_1.default.join(this.data.videoDir, `${failed ? "failed-" : ""}${name}.webm`); fs_1.default.mkdirSync(this.data.videoDir, { recursive: true }); shouldSaveVideo ? fs_1.default.renameSync(rawPath, finalPath) : fs_1.default.unlinkSync(rawPath); console.log(`${shouldSaveVideo ? "๐ŸŽฅ Video saved" : "๐Ÿงน Deleted video"}: ${finalPath}`); } } } catch (err) { console.warn(`โš ๏ธ Video error: ${err.message}`); } } // ๐Ÿงช Tracing if (this.context && this.data.tracingStarted) { const tracePath = path_1.default.join(this.data.artifactDir, "traces", `${name}.zip`); try { fs_1.default.mkdirSync(path_1.default.dirname(tracePath), { recursive: true }); await this.context.tracing.stop({ path: tracePath }); shouldSaveTrace ? console.log(`๐Ÿ“ฆ Trace saved: ${tracePath}`) : (fs_1.default.existsSync(tracePath) && fs_1.default.unlinkSync(tracePath), console.log(`๐Ÿงน Trace discarded: ${tracePath}`)); } catch (err) { console.warn("โŒ Trace handling error:", err); } } // ๐Ÿงช Visual regression if (this.page && this.data.enableVisualTest) { const BASELINE_DIR = path_1.default.resolve(this.data.artifactDir, "snapshots/baseline"); const DIFF_DIR = path_1.default.resolve(this.data.artifactDir, "snapshots/diff"); fs_1.default.mkdirSync(BASELINE_DIR, { recursive: true }); fs_1.default.mkdirSync(DIFF_DIR, { recursive: true }); const baselinePath = path_1.default.join(BASELINE_DIR, `${name}.png`); const actualPath = path_1.default.join(DIFF_DIR, `${name}.actual.png`); const diffPath = path_1.default.join(DIFF_DIR, `${name}.diff.png`); await this.page.screenshot({ path: actualPath, fullPage: true }); if (!fs_1.default.existsSync(baselinePath)) { fs_1.default.copyFileSync(actualPath, baselinePath); console.log(`๐Ÿ“ธ Created baseline image: ${baselinePath}`); } else { try { const diffPixels = (0, compareSnapshots_1.compareSnapshots)({ actualPath, baselinePath, diffPath, threshold: 0.1, }); console.log(diffPixels > 0 ? `โš ๏ธ Visual diff found (${diffPixels} pixels): ${diffPath}` : "โœ… No visual changes detected"); } catch (err) { console.warn("โŒ Snapshot comparison failed:", err); } } } // Cleanup try { await this.cleanup(scenario); } catch (err) { this.log?.("โŒ Error during cleanup: " + err.message); } });