simple-playwright-framework 0.0.16 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,7 +10,7 @@ exports.dataFixture = {
10
10
  let td;
11
11
  try {
12
12
  td = (0, data_loader_1.loadTestData)(testInfo, envName);
13
- console.log("Loaded test data:", td, "Type:", Array.isArray(td) ? "array" : typeof td);
13
+ //console.log("Loaded test data:", td, "Type:", Array.isArray(td) ? "array" : typeof td);
14
14
  }
15
15
  catch (err) {
16
16
  console.error(`❌ loadTestData threw for env '${envName}':`, err);
@@ -1,6 +1,7 @@
1
1
  import { Page } from "@playwright/test";
2
2
  import { Fixtures } from "../types/fixtures";
3
3
  export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & Fixtures & {
4
+ pc: Record<string, any>;
4
5
  authStore: (page: Page, creds: {
5
6
  username: string;
6
7
  password: string;
@@ -1,4 +1,12 @@
1
1
  "use strict";
2
+ // ════════════════════════════════════════════════════════════════
3
+ // framework/src/fixtures/index.ts
4
+ //
5
+ // MODIFIED — added projectConfigFixture alongside existing fixtures.
6
+ // Only this block is new:
7
+ // ...projectConfigFixture,
8
+ // Everything else is unchanged from your original file.
9
+ // ════════════════════════════════════════════════════════════════
2
10
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
11
  if (k2 === undefined) k2 = k;
4
12
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -19,13 +27,15 @@ const test_1 = require("@playwright/test");
19
27
  const envConfig_fixture_1 = require("./envConfig.fixture");
20
28
  const data_fixture_1 = require("./data.fixture");
21
29
  const testrail_fixture_1 = require("./testrail.fixture");
22
- const initAuthSession_1 = require("../utils/auth-session/initAuthSession");
23
30
  const file_fixture_1 = require("./file.fixture");
31
+ const projectConfig_fixture_1 = require("./projectConfig.fixture"); // ← NEW
32
+ const initAuthSession_1 = require("../utils/auth-session/initAuthSession");
24
33
  exports.test = test_1.test.extend({
25
34
  ...envConfig_fixture_1.envConfigFixture,
26
35
  ...data_fixture_1.dataFixture,
27
36
  ...testrail_fixture_1.testrailFixture,
28
37
  ...file_fixture_1.fileFixture,
38
+ ...projectConfig_fixture_1.projectConfigFixture, // ← NEW
29
39
  authStore: async ({ envConfig }, use) => {
30
40
  await use(async (page, creds, providerRegistry) => {
31
41
  if (!envConfig.authStorage) {
@@ -37,4 +47,4 @@ exports.test = test_1.test.extend({
37
47
  });
38
48
  var test_2 = require("@playwright/test");
39
49
  Object.defineProperty(exports, "expect", { enumerable: true, get: function () { return test_2.expect; } });
40
- __exportStar(require("../loaders/scenario.loader"), exports); // keep scenarioLoader export
50
+ __exportStar(require("../loaders/scenario.loader"), exports); // NEW
@@ -0,0 +1,6 @@
1
+ import { Page } from "@playwright/test";
2
+ export declare const projectConfigFixture: {
3
+ pc: ({ page }: {
4
+ page: Page;
5
+ }, use: (config: Record<string, any>) => Promise<void>) => Promise<void>;
6
+ };
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.projectConfigFixture = void 0;
4
+ const projectConfig_loader_1 = require("../loaders/projectConfig.loader");
5
+ exports.projectConfigFixture = {
6
+ pc: async ({ page }, use) => {
7
+ const config = (0, projectConfig_loader_1.loadProjectConfig)();
8
+ await use(config);
9
+ },
10
+ };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { initAuthSession } from "./utils/auth-session/initAuthSession";
2
+ export { initApiAuthSession } from "./utils/auth-session/initApiAuthSession";
2
3
  export { test, expect } from "./fixtures/index";
3
4
  export { scenarioLoader } from "./loaders/scenario.loader";
4
5
  export { loadConfig } from "./loaders/envConfig.loader";
@@ -7,4 +8,4 @@ export { envConfigFixture } from "./fixtures/envConfig.fixture";
7
8
  export { dataFixture } from "./fixtures/data.fixture";
8
9
  export { fileFixture } from "./fixtures/file.fixture";
9
10
  export { testrailFixture } from "./fixtures/testrail.fixture";
10
- export type { AuthProvider, AuthStorageConfig } from "./types/auth";
11
+ export type { AuthProvider, ApiAuthProvider, AuthStorageConfig } from "./types/auth";
package/dist/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.testrailFixture = exports.fileFixture = exports.dataFixture = exports.envConfigFixture = exports.FileUtils = exports.loadConfig = exports.scenarioLoader = exports.expect = exports.test = exports.initAuthSession = void 0;
3
+ exports.testrailFixture = exports.fileFixture = exports.dataFixture = exports.envConfigFixture = exports.FileUtils = exports.loadConfig = exports.scenarioLoader = exports.expect = exports.test = exports.initApiAuthSession = exports.initAuthSession = void 0;
4
4
  var initAuthSession_1 = require("./utils/auth-session/initAuthSession");
5
5
  Object.defineProperty(exports, "initAuthSession", { enumerable: true, get: function () { return initAuthSession_1.initAuthSession; } });
6
+ var initApiAuthSession_1 = require("./utils/auth-session/initApiAuthSession");
7
+ Object.defineProperty(exports, "initApiAuthSession", { enumerable: true, get: function () { return initApiAuthSession_1.initApiAuthSession; } });
6
8
  var index_1 = require("./fixtures/index");
7
9
  Object.defineProperty(exports, "test", { enumerable: true, get: function () { return index_1.test; } });
8
10
  Object.defineProperty(exports, "expect", { enumerable: true, get: function () { return index_1.expect; } });
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Loads and returns the contents of config/projectConfig.json
3
+ * as a plain object. The shape is defined by each project —
4
+ * the framework imposes no type constraints here.
5
+ *
6
+ * @returns Plain object from projectConfig.json
7
+ * @throws If the file is missing or contains invalid JSON
8
+ */
9
+ export declare function loadProjectConfig(): Record<string, any>;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ // ════════════════════════════════════════════════════════════════
3
+ // framework/src/loaders/projectConfig.loader.ts
4
+ //
5
+ // Loads projectConfig.json from the consuming project's
6
+ // config/ directory. Follows the exact same pattern as
7
+ // envConfig.loader.ts — flat JSON, no env nesting.
8
+ //
9
+ // projectConfig.json is for project-wide constants that are
10
+ // not environment-specific (threshold, schema paths, etc).
11
+ // Anything env-specific belongs in environments.json instead.
12
+ // ════════════════════════════════════════════════════════════════
13
+ var __importDefault = (this && this.__importDefault) || function (mod) {
14
+ return (mod && mod.__esModule) ? mod : { "default": mod };
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.loadProjectConfig = loadProjectConfig;
18
+ const fs_1 = __importDefault(require("fs"));
19
+ const path_1 = __importDefault(require("path"));
20
+ /**
21
+ * Loads and returns the contents of config/projectConfig.json
22
+ * as a plain object. The shape is defined by each project —
23
+ * the framework imposes no type constraints here.
24
+ *
25
+ * @returns Plain object from projectConfig.json
26
+ * @throws If the file is missing or contains invalid JSON
27
+ */
28
+ function loadProjectConfig() {
29
+ const configPath = path_1.default.join(process.cwd(), "config", "projectConfig.json");
30
+ if (!fs_1.default.existsSync(configPath)) {
31
+ throw new Error(`❌ projectConfig.json not found at ${configPath}\n` +
32
+ ` Create config/projectConfig.json in your project root.`);
33
+ }
34
+ const raw = fs_1.default.readFileSync(configPath, "utf-8");
35
+ try {
36
+ return JSON.parse(raw);
37
+ }
38
+ catch (e) {
39
+ throw new Error(`❌ projectConfig.json contains invalid JSON at ${configPath}\n${e}`);
40
+ }
41
+ }
@@ -2,6 +2,9 @@ import { Page } from "@playwright/test";
2
2
  export interface AuthProvider {
3
3
  login(page: Page): Promise<void>;
4
4
  }
5
+ export interface ApiAuthProvider {
6
+ getToken(request: any): Promise<string>;
7
+ }
5
8
  export interface AuthStorageConfig {
6
9
  enabled: boolean;
7
10
  validityMinutes: number;
@@ -1,5 +1,6 @@
1
1
  import type { AuthStorageConfig } from "./auth";
2
2
  export interface EnvConfig {
3
+ openAiApiKey: string;
3
4
  baseUrl: string;
4
5
  apiUrl?: string;
5
6
  db?: {
@@ -0,0 +1,20 @@
1
+ import { ApiAuthProvider, AuthStorageConfig } from "../../types/auth";
2
+ /**
3
+ * Manages API token authentication with optional token caching.
4
+ *
5
+ * Flow:
6
+ * 1. If storage disabled → call getToken() directly, no caching
7
+ * 2. If valid cached token exists → return it, skip login call
8
+ * 3. If no valid token → call getToken(), save to file, return token
9
+ *
10
+ * Storage format: { token: "...", savedAt: 1234567890 }
11
+ *
12
+ * @returns JWT token string ready to use in Authorization header
13
+ */
14
+ export declare function initApiAuthSession(request: any, authStorage: AuthStorageConfig | undefined, creds: {
15
+ username: string;
16
+ password: string;
17
+ }, providerRegistry: Record<string, new (creds: {
18
+ username: string;
19
+ password: string;
20
+ }) => ApiAuthProvider>): Promise<string>;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.initApiAuthSession = initApiAuthSession;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const storagePath_1 = require("./storagePath");
9
+ const validateStore_1 = require("./validateStore");
10
+ const resolveProvider_1 = require("./resolveProvider");
11
+ /**
12
+ * Manages API token authentication with optional token caching.
13
+ *
14
+ * Flow:
15
+ * 1. If storage disabled → call getToken() directly, no caching
16
+ * 2. If valid cached token exists → return it, skip login call
17
+ * 3. If no valid token → call getToken(), save to file, return token
18
+ *
19
+ * Storage format: { token: "...", savedAt: 1234567890 }
20
+ *
21
+ * @returns JWT token string ready to use in Authorization header
22
+ */
23
+ async function initApiAuthSession(request, authStorage, creds, providerRegistry) {
24
+ const ProviderClass = (0, resolveProvider_1.resolveProvider)(authStorage?.provider ?? "default", providerRegistry);
25
+ const authProvider = new ProviderClass(creds);
26
+ // ── Storage disabled → get token directly ────────────────────
27
+ if (!authStorage?.enabled) {
28
+ console.log("[Framework] API auth storage disabled — performing direct login");
29
+ return authProvider.getToken(request);
30
+ }
31
+ const { validityMinutes, provider } = authStorage;
32
+ const envName = process.env.TEST_ENV || "default";
33
+ const storagePath = (0, storagePath_1.getStoragePath)(provider, envName, creds.username);
34
+ // ── Valid cached token exists → return it ────────────────────
35
+ if ((0, validateStore_1.isAuthStoreValid)(storagePath, validityMinutes)) {
36
+ const saved = JSON.parse(fs_1.default.readFileSync(storagePath, "utf-8"));
37
+ console.log(`[Framework] ✅ Using cached API token for: ${creds.username}`);
38
+ return saved.token;
39
+ }
40
+ // ── No valid token → fresh login + save ──────────────────────
41
+ console.log("[Framework] Performing fresh API login...");
42
+ const token = await authProvider.getToken(request);
43
+ fs_1.default.writeFileSync(storagePath, JSON.stringify({
44
+ token,
45
+ savedAt: Date.now(),
46
+ }, null, 2));
47
+ console.log(`[Framework] New API token cached: ${storagePath}`);
48
+ return token;
49
+ }
@@ -1,5 +1,15 @@
1
1
  import { Page } from "@playwright/test";
2
2
  import { AuthProvider, AuthStorageConfig } from "../../types/auth";
3
+ /**
4
+ * Manages UI authentication with optional session storage.
5
+ *
6
+ * Flow:
7
+ * 1. If storage disabled → login directly, no caching
8
+ * 2. If valid stored session exists → restore it, skip login
9
+ * 3. If no valid session → perform fresh login, save session
10
+ *
11
+ * Supports both localStorage token apps (Nexus) and cookie-based apps.
12
+ */
3
13
  export declare function initAuthSession(page: Page, authStorage: AuthStorageConfig | undefined, creds: {
4
14
  username: string;
5
15
  password: string;
@@ -1,41 +1,62 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.initAuthSession = initAuthSession;
7
+ const fs_1 = __importDefault(require("fs"));
4
8
  const storagePath_1 = require("./storagePath");
5
9
  const validateStore_1 = require("./validateStore");
10
+ const resolveProvider_1 = require("./resolveProvider");
11
+ const restoreSession_1 = require("./restoreSession");
12
+ /**
13
+ * Manages UI authentication with optional session storage.
14
+ *
15
+ * Flow:
16
+ * 1. If storage disabled → login directly, no caching
17
+ * 2. If valid stored session exists → restore it, skip login
18
+ * 3. If no valid session → perform fresh login, save session
19
+ *
20
+ * Supports both localStorage token apps (Nexus) and cookie-based apps.
21
+ */
6
22
  async function initAuthSession(page, authStorage, creds, providerRegistry) {
23
+ const ProviderClass = (0, resolveProvider_1.resolveProvider)(authStorage?.provider ?? "default", providerRegistry);
24
+ const authProvider = new ProviderClass(creds);
25
+ // ── Storage disabled → login directly ────────────────────────
7
26
  if (!authStorage?.enabled) {
8
- console.log("[Framework] Auth storage disabled");
9
- const ProviderClass = providerRegistry[authStorage?.provider ?? "OrangeHRMLogin"];
10
- const authProvider = new ProviderClass(creds);
27
+ console.log("[Framework] Auth storage disabled — performing direct login");
11
28
  await authProvider.login(page);
12
29
  return;
13
30
  }
14
31
  const { validityMinutes, provider } = authStorage;
15
32
  const envName = process.env.TEST_ENV || "default";
16
33
  const storagePath = (0, storagePath_1.getStoragePath)(provider, envName, creds.username);
17
- const ProviderClass = providerRegistry[provider];
18
- if (!ProviderClass)
19
- throw new Error(`[Framework] Unknown provider: ${provider}`);
20
- const authProvider = new ProviderClass(creds);
21
- let needsLogin = true;
34
+ // ── Valid session exists → restore it ────────────────────────
22
35
  if ((0, validateStore_1.isAuthStoreValid)(storagePath, validityMinutes)) {
23
- console.log(`[Framework] Found valid auth store: ${storagePath}`);
24
- const state = JSON.parse(require("fs").readFileSync(storagePath, "utf-8"));
25
- await page.context().addCookies(state.cookies);
26
- await page.reload();
27
- if (!page.url().includes("/auth/login")) {
28
- console.log(`[Framework] Reusing valid auth store`);
29
- needsLogin = false;
30
- }
31
- else {
32
- console.log("[Framework] Auth store rejected, will re-login...");
33
- }
36
+ console.log(`[Framework] Restoring valid auth store: ${storagePath}`);
37
+ await (0, restoreSession_1.restoreSession)(page, storagePath);
38
+ console.log(`[Framework] ✅ Session restored from storage`);
39
+ return;
34
40
  }
35
- if (needsLogin) {
36
- console.log("[Framework] Performing fresh login...");
37
- await authProvider.login(page);
41
+ // ── No valid session → fresh login + save ────────────────────
42
+ console.log("[Framework] Performing fresh login...");
43
+ await authProvider.login(page);
44
+ // Save token + user from localStorage if present (token-based apps)
45
+ const saved = await page.evaluate(() => ({
46
+ token: localStorage.getItem("token"),
47
+ user: localStorage.getItem("user"),
48
+ }));
49
+ if (saved.token) {
50
+ // Token-based app — save token + user
51
+ fs_1.default.writeFileSync(storagePath, JSON.stringify({
52
+ token: saved.token,
53
+ user: saved.user ? JSON.parse(saved.user) : null,
54
+ savedAt: Date.now(),
55
+ }, null, 2));
56
+ }
57
+ else {
58
+ // Cookie-based app — save full Playwright storageState
38
59
  await page.context().storageState({ path: storagePath });
39
- console.log(`[Framework] New auth store created: ${storagePath}`);
40
60
  }
61
+ console.log(`[Framework] New auth store created: ${storagePath}`);
41
62
  }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Looks up a provider class from the registry by name.
3
+ * Throws a clear error if the provider is not registered.
4
+ *
5
+ * Used by both initAuthSession (UI) and initApiAuthSession (API).
6
+ */
7
+ export declare function resolveProvider<T>(providerName: string, providerRegistry: Record<string, new (creds: {
8
+ username: string;
9
+ password: string;
10
+ }) => T>): new (creds: {
11
+ username: string;
12
+ password: string;
13
+ }) => T;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveProvider = resolveProvider;
4
+ /**
5
+ * Looks up a provider class from the registry by name.
6
+ * Throws a clear error if the provider is not registered.
7
+ *
8
+ * Used by both initAuthSession (UI) and initApiAuthSession (API).
9
+ */
10
+ function resolveProvider(providerName, providerRegistry) {
11
+ const ProviderClass = providerRegistry[providerName];
12
+ if (!ProviderClass) {
13
+ throw new Error(`[Framework] Unknown auth provider: "${providerName}"\n` +
14
+ ` Registered providers: ${Object.keys(providerRegistry).join(", ")}`);
15
+ }
16
+ return ProviderClass;
17
+ }
@@ -0,0 +1,14 @@
1
+ import { Page } from "@playwright/test";
2
+ /**
3
+ * Restores a saved auth session into the browser page.
4
+ *
5
+ * Supports two formats:
6
+ *
7
+ * 1. localStorage token format: { token, user }
8
+ * Injects token and user into localStorage via addInitScript
9
+ * so they are available before the page loads.
10
+ *
11
+ * 2. Playwright storageState (cookies/origins):
12
+ * Restores cookies via addCookies — for cookie-based apps.
13
+ */
14
+ export declare function restoreSession(page: Page, storagePath: string): Promise<void>;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.restoreSession = restoreSession;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ /**
9
+ * Restores a saved auth session into the browser page.
10
+ *
11
+ * Supports two formats:
12
+ *
13
+ * 1. localStorage token format: { token, user }
14
+ * Injects token and user into localStorage via addInitScript
15
+ * so they are available before the page loads.
16
+ *
17
+ * 2. Playwright storageState (cookies/origins):
18
+ * Restores cookies via addCookies — for cookie-based apps.
19
+ */
20
+ async function restoreSession(page, storagePath) {
21
+ const raw = fs_1.default.readFileSync(storagePath, "utf-8").trim();
22
+ const state = JSON.parse(raw);
23
+ // ── localStorage token format (Nexus and token-based apps) ───
24
+ if (state.token) {
25
+ await page.addInitScript((saved) => {
26
+ localStorage.setItem("token", saved.token);
27
+ if (saved.user)
28
+ localStorage.setItem("user", JSON.stringify(saved.user));
29
+ }, { token: state.token, user: state.user ?? null });
30
+ return;
31
+ }
32
+ // ── Playwright cookie-based storageState ─────────────────────
33
+ if (state.cookies?.length > 0) {
34
+ await page.context().addCookies(state.cookies);
35
+ }
36
+ }
@@ -1 +1,7 @@
1
+ /**
2
+ * Builds the file path for storing auth state.
3
+ * Creates the storage directory if it doesn't exist.
4
+ *
5
+ * Format: storage/{provider}-{env}-{username}-auth.json
6
+ */
1
7
  export declare function getStoragePath(provider: string, envName: string, username: string): string;
@@ -6,6 +6,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getStoragePath = getStoragePath;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
+ /**
10
+ * Builds the file path for storing auth state.
11
+ * Creates the storage directory if it doesn't exist.
12
+ *
13
+ * Format: storage/{provider}-{env}-{username}-auth.json
14
+ */
9
15
  function getStoragePath(provider, envName, username) {
10
16
  const storageDir = path_1.default.resolve("storage");
11
17
  if (!fs_1.default.existsSync(storageDir))
@@ -1 +1,14 @@
1
+ /**
2
+ * Checks if a stored auth file is valid and not expired.
3
+ *
4
+ * Supports two storage formats:
5
+ *
6
+ * 1. UI / cookie-based (Playwright storageState):
7
+ * { cookies: [...], origins: [...] }
8
+ * Valid if cookies or origins are non-empty and file age < validityMinutes
9
+ *
10
+ * 2. API / token-based:
11
+ * { token: "...", savedAt: 1234567890 }
12
+ * Valid if token exists and (now - savedAt) < validityMinutes
13
+ */
1
14
  export declare function isAuthStoreValid(storagePath: string, validityMinutes: number): boolean;
@@ -5,6 +5,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.isAuthStoreValid = isAuthStoreValid;
7
7
  const fs_1 = __importDefault(require("fs"));
8
+ /**
9
+ * Checks if a stored auth file is valid and not expired.
10
+ *
11
+ * Supports two storage formats:
12
+ *
13
+ * 1. UI / cookie-based (Playwright storageState):
14
+ * { cookies: [...], origins: [...] }
15
+ * Valid if cookies or origins are non-empty and file age < validityMinutes
16
+ *
17
+ * 2. API / token-based:
18
+ * { token: "...", savedAt: 1234567890 }
19
+ * Valid if token exists and (now - savedAt) < validityMinutes
20
+ */
8
21
  function isAuthStoreValid(storagePath, validityMinutes) {
9
22
  if (!fs_1.default.existsSync(storagePath))
10
23
  return false;
@@ -13,9 +26,19 @@ function isAuthStoreValid(storagePath, validityMinutes) {
13
26
  return false;
14
27
  try {
15
28
  const state = JSON.parse(raw);
16
- const stats = fs_1.default.statSync(storagePath);
17
- const ageMinutes = (Date.now() - stats.mtimeMs) / 60000;
18
- return (state.cookies?.length > 0 || state.origins?.length > 0) && ageMinutes < validityMinutes;
29
+ // ── API token format ──────────────────────────────────────
30
+ if (state.token && state.savedAt) {
31
+ const ageMinutes = (Date.now() - state.savedAt) / 60000;
32
+ return ageMinutes < validityMinutes;
33
+ }
34
+ // ── UI / Playwright storageState format ───────────────────
35
+ if (state.cookies !== undefined || state.origins !== undefined) {
36
+ const hasContent = (state.cookies?.length > 0) || (state.origins?.length > 0);
37
+ const stats = fs_1.default.statSync(storagePath);
38
+ const ageMinutes = (Date.now() - stats.mtimeMs) / 60000;
39
+ return hasContent && ageMinutes < validityMinutes;
40
+ }
41
+ return false;
19
42
  }
20
43
  catch {
21
44
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simple-playwright-framework",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
4
4
  "description": "A modular Playwright framework with fixtures, loaders, and demo scaffolding.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",