playwright-cucumber-ts-steps 1.0.1 → 1.0.3
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.
- package/README.md +270 -244
- package/dist/backend/actions/formTable.js +34 -0
- package/dist/backend/actions/index.js +5 -0
- package/dist/backend/actions/interactions.js +23 -0
- package/dist/backend/actions/navigation.js +19 -0
- package/dist/backend/api/assertions.js +26 -0
- package/dist/backend/api/index.js +5 -0
- package/dist/backend/api/mock.js +75 -0
- package/dist/backend/api/requests.js +86 -0
- package/dist/backend/api/state.js +15 -0
- package/dist/backend/assertions/expectVisible.js +8 -0
- package/dist/backend/assertions/index.js +5 -0
- package/dist/backend/assertions/pageState.js +25 -0
- package/dist/backend/assertions/text.js +20 -0
- package/dist/backend/assertions/visibility.js +20 -0
- package/dist/backend/auth/index.js +71 -0
- package/dist/backend/db/index.js +6 -0
- package/dist/backend/db/state.js +24 -0
- package/dist/backend/db/steps.js +43 -0
- package/dist/backend/elements/alerts.js +21 -0
- package/dist/backend/elements/forms.js +59 -0
- package/dist/backend/elements/frames.js +25 -0
- package/dist/backend/elements/index.js +5 -0
- package/dist/core/registry.js +20 -0
- package/dist/core/runner.js +151 -0
- package/dist/index.js +10 -0
- package/dist/reporting/index.js +43 -0
- package/package.json +19 -101
- package/LICENSE +0 -21
- package/lib/actions/clickSteps.d.ts +0 -251
- package/lib/actions/clickSteps.js +0 -415
- package/lib/actions/cookieSteps.d.ts +0 -18
- package/lib/actions/cookieSteps.js +0 -93
- package/lib/actions/debugSteps.d.ts +0 -14
- package/lib/actions/debugSteps.js +0 -23
- package/lib/actions/elementFindSteps.d.ts +0 -668
- package/lib/actions/elementFindSteps.js +0 -931
- package/lib/actions/fillFormSteps.d.ts +0 -69
- package/lib/actions/fillFormSteps.js +0 -237
- package/lib/actions/index.d.ts +0 -11
- package/lib/actions/index.js +0 -28
- package/lib/actions/inputSteps.d.ts +0 -218
- package/lib/actions/inputSteps.js +0 -343
- package/lib/actions/interceptionSteps.d.ts +0 -169
- package/lib/actions/interceptionSteps.js +0 -291
- package/lib/actions/miscSteps.d.ts +0 -645
- package/lib/actions/miscSteps.js +0 -1061
- package/lib/actions/mouseSteps.d.ts +0 -143
- package/lib/actions/mouseSteps.js +0 -234
- package/lib/actions/scrollSteps.d.ts +0 -82
- package/lib/actions/scrollSteps.js +0 -123
- package/lib/actions/storageSteps.d.ts +0 -174
- package/lib/actions/storageSteps.js +0 -292
- package/lib/assertions/buttonAndTextVisibilitySteps.d.ts +0 -245
- package/lib/assertions/buttonAndTextVisibilitySteps.js +0 -401
- package/lib/assertions/cookieSteps.d.ts +0 -75
- package/lib/assertions/cookieSteps.js +0 -113
- package/lib/assertions/elementSteps.d.ts +0 -264
- package/lib/assertions/elementSteps.js +0 -388
- package/lib/assertions/formInputSteps.d.ts +0 -248
- package/lib/assertions/formInputSteps.js +0 -350
- package/lib/assertions/index.d.ts +0 -10
- package/lib/assertions/index.js +0 -27
- package/lib/assertions/interceptionRequestsSteps.d.ts +0 -353
- package/lib/assertions/interceptionRequestsSteps.js +0 -593
- package/lib/assertions/locationSteps.d.ts +0 -217
- package/lib/assertions/locationSteps.js +0 -310
- package/lib/assertions/roleTestIdSteps.d.ts +0 -159
- package/lib/assertions/roleTestIdSteps.js +0 -221
- package/lib/assertions/semanticSteps.d.ts +0 -176
- package/lib/assertions/semanticSteps.js +0 -252
- package/lib/assertions/storageSteps.d.ts +0 -149
- package/lib/assertions/storageSteps.js +0 -210
- package/lib/assertions/visualSteps.d.ts +0 -74
- package/lib/assertions/visualSteps.js +0 -209
- package/lib/custom_setups/loginHooks.d.ts +0 -1
- package/lib/custom_setups/loginHooks.js +0 -130
- package/lib/helpers/checkPeerDeps.d.ts +0 -1
- package/lib/helpers/checkPeerDeps.js +0 -19
- package/lib/helpers/compareSnapshots.d.ts +0 -6
- package/lib/helpers/compareSnapshots.js +0 -20
- package/lib/helpers/hooks.d.ts +0 -1
- package/lib/helpers/hooks.js +0 -210
- package/lib/helpers/utils/fakerUtils.d.ts +0 -1
- package/lib/helpers/utils/fakerUtils.js +0 -60
- package/lib/helpers/utils/index.d.ts +0 -4
- package/lib/helpers/utils/index.js +0 -20
- package/lib/helpers/utils/optionsUtils.d.ts +0 -24
- package/lib/helpers/utils/optionsUtils.js +0 -88
- package/lib/helpers/utils/resolveUtils.d.ts +0 -6
- package/lib/helpers/utils/resolveUtils.js +0 -72
- package/lib/helpers/utils/sessionUtils.d.ts +0 -3
- package/lib/helpers/utils/sessionUtils.js +0 -40
- package/lib/helpers/world.d.ts +0 -34
- package/lib/helpers/world.js +0 -110
- package/lib/iframes/frames.d.ts +0 -1
- package/lib/iframes/frames.js +0 -11
- package/lib/index.d.ts +0 -10
- package/lib/index.js +0 -28
- package/lib/register.d.ts +0 -1
- package/lib/register.js +0 -6
- package/src/actions/clickSteps.ts +0 -429
- package/src/actions/cookieSteps.ts +0 -95
- package/src/actions/debugSteps.ts +0 -21
- package/src/actions/elementFindSteps.ts +0 -961
- package/src/actions/fillFormSteps.ts +0 -270
- package/src/actions/index.ts +0 -12
- package/src/actions/inputSteps.ts +0 -354
- package/src/actions/interceptionSteps.ts +0 -325
- package/src/actions/miscSteps.ts +0 -1144
- package/src/actions/mouseSteps.ts +0 -256
- package/src/actions/scrollSteps.ts +0 -122
- package/src/actions/storageSteps.ts +0 -308
- package/src/assertions/buttonAndTextVisibilitySteps.ts +0 -436
- package/src/assertions/cookieSteps.ts +0 -131
- package/src/assertions/elementSteps.ts +0 -432
- package/src/assertions/formInputSteps.ts +0 -377
- package/src/assertions/index.ts +0 -11
- package/src/assertions/interceptionRequestsSteps.ts +0 -640
- package/src/assertions/locationSteps.ts +0 -315
- package/src/assertions/roleTestIdSteps.ts +0 -254
- package/src/assertions/semanticSteps.ts +0 -267
- package/src/assertions/storageSteps.ts +0 -250
- package/src/assertions/visualSteps.ts +0 -275
- package/src/custom_setups/loginHooks.ts +0 -154
- package/src/helpers/checkPeerDeps.ts +0 -19
- package/src/helpers/compareSnapshots.ts +0 -35
- package/src/helpers/hooks.ts +0 -212
- package/src/helpers/utils/fakerUtils.ts +0 -64
- package/src/helpers/utils/index.ts +0 -4
- package/src/helpers/utils/optionsUtils.ts +0 -104
- package/src/helpers/utils/resolveUtils.ts +0 -74
- package/src/helpers/utils/sessionUtils.ts +0 -36
- package/src/helpers/world.ts +0 -119
- package/src/iframes/frames.ts +0 -15
- package/src/index.ts +0 -18
- package/src/register.ts +0 -4
|
@@ -1,130 +0,0 @@
|
|
|
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
|
-
const fs_1 = __importDefault(require("fs"));
|
|
7
|
-
const cucumber_1 = require("@cucumber/cucumber");
|
|
8
|
-
const resolveUtils_1 = require("../helpers/utils/resolveUtils");
|
|
9
|
-
// Step 1: Check and load existing session if valid
|
|
10
|
-
(0, cucumber_1.When)("I login with a session data {string}", async function (sessionName) {
|
|
11
|
-
const sessionPath = (0, resolveUtils_1.resolveSessionPath)(this, sessionName);
|
|
12
|
-
this.data.sessionFile = sessionPath;
|
|
13
|
-
if (fs_1.default.existsSync(sessionPath)) {
|
|
14
|
-
try {
|
|
15
|
-
await this.context?.addCookies(JSON.parse(fs_1.default.readFileSync(sessionPath, "utf-8")).cookies || []);
|
|
16
|
-
this.log?.(`✅ Loaded session from ${sessionPath}`);
|
|
17
|
-
}
|
|
18
|
-
catch (err) {
|
|
19
|
-
this.log?.(`⚠️ Failed to apply session: ${err.message}`);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
this.log?.(`⚠️ Session file not found: ${sessionPath}`);
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
/**
|
|
27
|
-
* @step
|
|
28
|
-
* @description Saves the current browser context (cookies, localStorage, sessionStorage) as a session file.
|
|
29
|
-
* @example
|
|
30
|
-
* When I save session as "my-session"
|
|
31
|
-
*/
|
|
32
|
-
(0, cucumber_1.When)(/^I save session as "([^"]+)"$/, async function (sessionName) {
|
|
33
|
-
const sessionPath = (0, resolveUtils_1.resolveSessionPath)(this, sessionName);
|
|
34
|
-
const cookies = await this.context.cookies();
|
|
35
|
-
const [localStorageData, sessionStorageData] = await this.page.evaluate(() => {
|
|
36
|
-
const toPairs = (store) => Object.entries(store);
|
|
37
|
-
return [
|
|
38
|
-
[{ origin: location.origin, values: toPairs(localStorage) }],
|
|
39
|
-
[{ origin: location.origin, values: toPairs(sessionStorage) }],
|
|
40
|
-
];
|
|
41
|
-
});
|
|
42
|
-
const sessionData = {
|
|
43
|
-
cookies,
|
|
44
|
-
localStorage: localStorageData,
|
|
45
|
-
sessionStorage: sessionStorageData,
|
|
46
|
-
};
|
|
47
|
-
try {
|
|
48
|
-
fs_1.default.writeFileSync(sessionPath, JSON.stringify(sessionData, null, 2));
|
|
49
|
-
this.log?.(`💾 Saved session as "${sessionName}"`);
|
|
50
|
-
}
|
|
51
|
-
catch (err) {
|
|
52
|
-
this.log?.(`❌ Failed to save session "${sessionName}": ${err.message}`);
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
/**
|
|
56
|
-
* @step
|
|
57
|
-
* @description Removes a session file with the given name.
|
|
58
|
-
* @example
|
|
59
|
-
* When I clear session "my-session"
|
|
60
|
-
*/
|
|
61
|
-
(0, cucumber_1.When)("I clear session {string}", function (sessionName) {
|
|
62
|
-
const sessionPath = (0, resolveUtils_1.resolveSessionPath)(this, sessionName);
|
|
63
|
-
if (fs_1.default.existsSync(sessionPath)) {
|
|
64
|
-
fs_1.default.unlinkSync(sessionPath);
|
|
65
|
-
this.log?.(`🧹 Cleared session: ${sessionPath}`);
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
this.log?.(`⚠️ Session not found: ${sessionPath}`);
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
/**
|
|
72
|
-
* @step
|
|
73
|
-
* @description Restores cookies, localStorage, and sessionStorage from a session file. Optionally reloads the page.
|
|
74
|
-
* @example
|
|
75
|
-
* When I restore session cookies "my-session"
|
|
76
|
-
* When I restore session cookies "my-session" with reload "false"
|
|
77
|
-
*/
|
|
78
|
-
(0, cucumber_1.When)(/^I restore session cookies "([^"]+)"(?: with reload "(true|false)")?$/, async function (sessionName, reload = "true") {
|
|
79
|
-
const sessionPath = (0, resolveUtils_1.resolveSessionPath)(this, sessionName);
|
|
80
|
-
if (!fs_1.default.existsSync(sessionPath)) {
|
|
81
|
-
this.log?.(`❌ Session file not found: ${sessionPath}`);
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
const sessionData = JSON.parse(fs_1.default.readFileSync(sessionPath, "utf-8"));
|
|
85
|
-
const { cookies = [], localStorage = [], sessionStorage = [] } = sessionData;
|
|
86
|
-
try {
|
|
87
|
-
// Clear & set cookies
|
|
88
|
-
if (cookies.length) {
|
|
89
|
-
const existing = await this.context.cookies();
|
|
90
|
-
if (existing.length)
|
|
91
|
-
await this.context.clearCookies();
|
|
92
|
-
await this.context.addCookies(cookies);
|
|
93
|
-
this.log?.(`🍪 Cookies restored from "${sessionName}"`);
|
|
94
|
-
}
|
|
95
|
-
// Apply storage into page context
|
|
96
|
-
await this.page.goto("about:blank");
|
|
97
|
-
if (localStorage.length > 0) {
|
|
98
|
-
for (const entry of localStorage) {
|
|
99
|
-
await this.page.addInitScript(([origin, values]) => {
|
|
100
|
-
if (window.origin === origin) {
|
|
101
|
-
for (const [key, val] of values) {
|
|
102
|
-
localStorage.setItem(key, val);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}, [entry.origin, entry.values]);
|
|
106
|
-
}
|
|
107
|
-
this.log?.("📦 localStorage restored");
|
|
108
|
-
}
|
|
109
|
-
if (sessionStorage.length > 0) {
|
|
110
|
-
for (const entry of sessionStorage) {
|
|
111
|
-
await this.page.addInitScript(([origin, values]) => {
|
|
112
|
-
if (window.origin === origin) {
|
|
113
|
-
for (const [key, val] of values) {
|
|
114
|
-
sessionStorage.setItem(key, val);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}, [entry.origin, entry.values]);
|
|
118
|
-
}
|
|
119
|
-
this.log?.("🗄️ sessionStorage restored");
|
|
120
|
-
}
|
|
121
|
-
// Final reload to apply context if requested
|
|
122
|
-
if (reload !== "false") {
|
|
123
|
-
await this.page.reload();
|
|
124
|
-
this.log?.("🔄 Page reloaded to apply restored session");
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
catch (err) {
|
|
128
|
-
this.log?.(`❌ Error restoring session: ${err.message}`);
|
|
129
|
-
}
|
|
130
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function checkPeerDependencies(dependencies: string[]): void;
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.checkPeerDependencies = checkPeerDependencies;
|
|
4
|
-
function checkPeerDependencies(dependencies) {
|
|
5
|
-
const missing = [];
|
|
6
|
-
for (const dep of dependencies) {
|
|
7
|
-
try {
|
|
8
|
-
require.resolve(dep);
|
|
9
|
-
}
|
|
10
|
-
catch {
|
|
11
|
-
missing.push(dep);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
if (missing.length) {
|
|
15
|
-
console.warn(`\n❌ Missing peer dependencies: ${missing.join(", ")}` +
|
|
16
|
-
`\nPlease install them in your project:\n\n` +
|
|
17
|
-
`npm install --save-dev ${missing.join(" ")}\n`);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
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.compareSnapshots = compareSnapshots;
|
|
7
|
-
const fs_1 = __importDefault(require("fs"));
|
|
8
|
-
const pixelmatch_1 = __importDefault(require("pixelmatch"));
|
|
9
|
-
const pngjs_1 = require("pngjs");
|
|
10
|
-
function compareSnapshots({ actualPath, baselinePath, diffPath, threshold = 0.1, }) {
|
|
11
|
-
const actual = pngjs_1.PNG.sync.read(fs_1.default.readFileSync(actualPath));
|
|
12
|
-
const baseline = pngjs_1.PNG.sync.read(fs_1.default.readFileSync(baselinePath));
|
|
13
|
-
if (actual.width !== baseline.width || actual.height !== baseline.height) {
|
|
14
|
-
throw new Error("Snapshot size mismatch");
|
|
15
|
-
}
|
|
16
|
-
const diff = new pngjs_1.PNG({ width: actual.width, height: actual.height });
|
|
17
|
-
const numDiffPixels = (0, pixelmatch_1.default)(actual.data, baseline.data, diff.data, actual.width, actual.height, { threshold });
|
|
18
|
-
fs_1.default.writeFileSync(diffPath, pngjs_1.PNG.sync.write(diff));
|
|
19
|
-
return numDiffPixels;
|
|
20
|
-
}
|
package/lib/helpers/hooks.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/lib/helpers/hooks.js
DELETED
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
const fs_1 = __importDefault(require("fs"));
|
|
40
|
-
const path_1 = __importDefault(require("path"));
|
|
41
|
-
const cucumber_1 = require("@cucumber/cucumber");
|
|
42
|
-
const dotenv = __importStar(require("dotenv"));
|
|
43
|
-
const playwright_1 = require("playwright");
|
|
44
|
-
const compareSnapshots_1 = require("./compareSnapshots");
|
|
45
|
-
// Set to 30 seconds
|
|
46
|
-
(0, cucumber_1.setDefaultTimeout)(30 * 1000);
|
|
47
|
-
dotenv.config();
|
|
48
|
-
let sharedBrowser;
|
|
49
|
-
(0, cucumber_1.BeforeAll)(async () => {
|
|
50
|
-
sharedBrowser = await playwright_1.chromium.launch({
|
|
51
|
-
headless: process.env.HEADLESS !== "false",
|
|
52
|
-
});
|
|
53
|
-
console.log("🚀 Launched shared browser for all scenarios");
|
|
54
|
-
});
|
|
55
|
-
(0, cucumber_1.AfterAll)(async () => {
|
|
56
|
-
await sharedBrowser?.close();
|
|
57
|
-
console.log("🧹 Closed shared browser after all scenarios");
|
|
58
|
-
});
|
|
59
|
-
(0, cucumber_1.Before)(async function (scenario) {
|
|
60
|
-
const params = this.parameters || {};
|
|
61
|
-
const ARTIFACT_DIR = params.artifactDir || process.env.TEST_ARTIFACT_DIR || "test-artifacts";
|
|
62
|
-
const SCREENSHOT_DIR = path_1.default.resolve(ARTIFACT_DIR, "screenshots");
|
|
63
|
-
const VIDEO_DIR = path_1.default.resolve(ARTIFACT_DIR, "videos");
|
|
64
|
-
const TRACE_DIR = path_1.default.resolve(ARTIFACT_DIR, "traces");
|
|
65
|
-
const SESSION_FILE = path_1.default.resolve(ARTIFACT_DIR, "auth-cookies", "session.json");
|
|
66
|
-
this.data.artifactDir = ARTIFACT_DIR;
|
|
67
|
-
this.data.screenshotDir = SCREENSHOT_DIR;
|
|
68
|
-
this.data.videoDir = VIDEO_DIR;
|
|
69
|
-
this.data.traceDir = TRACE_DIR;
|
|
70
|
-
this.data.sessionFile = SESSION_FILE;
|
|
71
|
-
// Modes: "false" | "fail" | "all"
|
|
72
|
-
const traceMode = (params.enableTrace || process.env.ENABLE_TRACE || "false").toLowerCase();
|
|
73
|
-
const screenshotMode = (params.enableScreenshots ||
|
|
74
|
-
process.env.ENABLE_SCREENSHOTS ||
|
|
75
|
-
"false").toLowerCase();
|
|
76
|
-
const videoMode = (params.enableVideos || process.env.ENABLE_VIDEOS || "false").toLowerCase();
|
|
77
|
-
this.data.traceMode = traceMode;
|
|
78
|
-
this.data.screenshotMode = screenshotMode;
|
|
79
|
-
this.data.videoMode = videoMode;
|
|
80
|
-
const isMobileTag = scenario.pickle.tags.some((t) => t.name === "@mobile");
|
|
81
|
-
const deviceName = params.device || process.env.MOBILE_DEVICE || (isMobileTag ? "iPhone 13 Pro" : null);
|
|
82
|
-
const deviceSettings = deviceName ? playwright_1.devices[deviceName] : undefined;
|
|
83
|
-
if (deviceName && !deviceSettings) {
|
|
84
|
-
throw new Error(`🚫 Invalid MOBILE_DEVICE: "${deviceName}" is not recognized by Playwright.`);
|
|
85
|
-
}
|
|
86
|
-
const isVisualTest = params.enableVisualTest ??
|
|
87
|
-
(process.env.ENABLE_VISUAL_TEST === "true" ||
|
|
88
|
-
scenario.pickle.tags.some((t) => t.name === "@visual"));
|
|
89
|
-
this.data.enableVisualTest = isVisualTest;
|
|
90
|
-
if (isVisualTest)
|
|
91
|
-
process.env.VISUAL_TEST = "true";
|
|
92
|
-
const contextOptions = {
|
|
93
|
-
...(videoMode !== "false" ? { recordVideo: { dir: VIDEO_DIR } } : {}),
|
|
94
|
-
...(deviceSettings || {}),
|
|
95
|
-
};
|
|
96
|
-
if (fs_1.default.existsSync(SESSION_FILE)) {
|
|
97
|
-
contextOptions.storageState = SESSION_FILE;
|
|
98
|
-
this.log?.("✅ Reusing session from saved file.");
|
|
99
|
-
}
|
|
100
|
-
const context = await sharedBrowser.newContext(contextOptions);
|
|
101
|
-
const page = await context.newPage();
|
|
102
|
-
this.browser = sharedBrowser;
|
|
103
|
-
this.context = context;
|
|
104
|
-
this.page = page;
|
|
105
|
-
if (traceMode !== "false") {
|
|
106
|
-
await context.tracing.start({
|
|
107
|
-
screenshots: true,
|
|
108
|
-
snapshots: true,
|
|
109
|
-
sources: true,
|
|
110
|
-
});
|
|
111
|
-
this.data.tracingStarted = true;
|
|
112
|
-
this.log?.(`🧪 Tracing started (${traceMode})`);
|
|
113
|
-
}
|
|
114
|
-
if (deviceName)
|
|
115
|
-
this.log?.(`📱 Mobile emulation enabled (${deviceName})`);
|
|
116
|
-
});
|
|
117
|
-
(0, cucumber_1.After)(async function (scenario) {
|
|
118
|
-
const name = scenario.pickle.name.replace(/[^a-z0-9]+/gi, "_").toLowerCase();
|
|
119
|
-
const failed = scenario.result?.status === "FAILED";
|
|
120
|
-
const mode = (value) => value?.toLowerCase();
|
|
121
|
-
const screenshotMode = mode(this.parameters?.enableScreenshots || process.env.ENABLE_SCREENSHOTS);
|
|
122
|
-
const videoMode = mode(this.parameters?.enableVideos || process.env.ENABLE_VIDEOS);
|
|
123
|
-
const traceMode = mode(this.parameters?.enableTrace || process.env.ENABLE_TRACE);
|
|
124
|
-
const shouldSaveScreenshot = screenshotMode === "all" || (screenshotMode === "fail" && failed);
|
|
125
|
-
const shouldSaveVideo = videoMode === "all" || (videoMode === "fail" && failed);
|
|
126
|
-
const shouldSaveTrace = traceMode === "all" || (traceMode === "fail" && failed);
|
|
127
|
-
// 📸 Screenshot
|
|
128
|
-
if (shouldSaveScreenshot && this.page) {
|
|
129
|
-
const screenshotPath = path_1.default.join(this.data.screenshotDir, `${failed ? "failed-" : ""}${name}.png`);
|
|
130
|
-
try {
|
|
131
|
-
fs_1.default.mkdirSync(this.data.screenshotDir, { recursive: true });
|
|
132
|
-
await this.page.screenshot({ path: screenshotPath, fullPage: true });
|
|
133
|
-
console.log(`🖼️ Screenshot saved: ${screenshotPath}`);
|
|
134
|
-
}
|
|
135
|
-
catch (err) {
|
|
136
|
-
console.warn("❌ Failed to save screenshot:", err);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
// 🎥 Video
|
|
140
|
-
if (this.page && videoMode !== "false") {
|
|
141
|
-
try {
|
|
142
|
-
const video = this.page.video();
|
|
143
|
-
if (video) {
|
|
144
|
-
const rawPath = await video.path();
|
|
145
|
-
if (fs_1.default.existsSync(rawPath)) {
|
|
146
|
-
const finalPath = path_1.default.join(this.data.videoDir, `${failed ? "failed-" : ""}${name}.webm`);
|
|
147
|
-
fs_1.default.mkdirSync(this.data.videoDir, { recursive: true });
|
|
148
|
-
shouldSaveVideo ? fs_1.default.renameSync(rawPath, finalPath) : fs_1.default.unlinkSync(rawPath);
|
|
149
|
-
console.log(`${shouldSaveVideo ? "🎥 Video saved" : "🧹 Deleted video"}: ${finalPath}`);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
catch (err) {
|
|
154
|
-
console.warn(`⚠️ Video error: ${err.message}`);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
// 🧪 Tracing
|
|
158
|
-
if (this.context && this.data.tracingStarted) {
|
|
159
|
-
const tracePath = path_1.default.join(this.data.artifactDir, "traces", `${name}.zip`);
|
|
160
|
-
try {
|
|
161
|
-
fs_1.default.mkdirSync(path_1.default.dirname(tracePath), { recursive: true });
|
|
162
|
-
await this.context.tracing.stop({ path: tracePath });
|
|
163
|
-
shouldSaveTrace
|
|
164
|
-
? console.log(`📦 Trace saved: ${tracePath}`)
|
|
165
|
-
: (fs_1.default.existsSync(tracePath) && fs_1.default.unlinkSync(tracePath),
|
|
166
|
-
console.log(`🧹 Trace discarded: ${tracePath}`));
|
|
167
|
-
}
|
|
168
|
-
catch (err) {
|
|
169
|
-
console.warn("❌ Trace handling error:", err);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
// 🧪 Visual regression
|
|
173
|
-
if (this.page && this.data.enableVisualTest) {
|
|
174
|
-
const BASELINE_DIR = path_1.default.resolve(this.data.artifactDir, "snapshots/baseline");
|
|
175
|
-
const DIFF_DIR = path_1.default.resolve(this.data.artifactDir, "snapshots/diff");
|
|
176
|
-
fs_1.default.mkdirSync(BASELINE_DIR, { recursive: true });
|
|
177
|
-
fs_1.default.mkdirSync(DIFF_DIR, { recursive: true });
|
|
178
|
-
const baselinePath = path_1.default.join(BASELINE_DIR, `${name}.png`);
|
|
179
|
-
const actualPath = path_1.default.join(DIFF_DIR, `${name}.actual.png`);
|
|
180
|
-
const diffPath = path_1.default.join(DIFF_DIR, `${name}.diff.png`);
|
|
181
|
-
await this.page.screenshot({ path: actualPath, fullPage: true });
|
|
182
|
-
if (!fs_1.default.existsSync(baselinePath)) {
|
|
183
|
-
fs_1.default.copyFileSync(actualPath, baselinePath);
|
|
184
|
-
console.log(`📸 Created baseline image: ${baselinePath}`);
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
try {
|
|
188
|
-
const diffPixels = (0, compareSnapshots_1.compareSnapshots)({
|
|
189
|
-
actualPath,
|
|
190
|
-
baselinePath,
|
|
191
|
-
diffPath,
|
|
192
|
-
threshold: 0.1,
|
|
193
|
-
});
|
|
194
|
-
console.log(diffPixels > 0
|
|
195
|
-
? `⚠️ Visual diff found (${diffPixels} pixels): ${diffPath}`
|
|
196
|
-
: "✅ No visual changes detected");
|
|
197
|
-
}
|
|
198
|
-
catch (err) {
|
|
199
|
-
console.warn("❌ Snapshot comparison failed:", err);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
// Cleanup
|
|
204
|
-
try {
|
|
205
|
-
await this.cleanup(scenario);
|
|
206
|
-
}
|
|
207
|
-
catch (err) {
|
|
208
|
-
this.log?.("❌ Error during cleanup: " + err.message);
|
|
209
|
-
}
|
|
210
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function evaluateFaker(value: string): string;
|
|
@@ -1,60 +0,0 @@
|
|
|
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.evaluateFaker = evaluateFaker;
|
|
7
|
-
const faker_1 = require("@faker-js/faker");
|
|
8
|
-
const dayjs_1 = __importDefault(require("dayjs"));
|
|
9
|
-
const fakerMapping = {
|
|
10
|
-
"First Name": () => faker_1.faker.person.middleName(),
|
|
11
|
-
Name: () => faker_1.faker.person.middleName(),
|
|
12
|
-
"Last Name": () => faker_1.faker.person.middleName(),
|
|
13
|
-
Email: () => faker_1.faker.internet.email(),
|
|
14
|
-
"Phone Number": () => faker_1.faker.string.numeric(10),
|
|
15
|
-
Number: () => faker_1.faker.string.numeric(11),
|
|
16
|
-
"Complete Number": () => faker_1.faker.string.numeric(11),
|
|
17
|
-
"App Colour": () => faker_1.faker.color.rgb(),
|
|
18
|
-
"App Name": () => faker_1.faker.commerce.productName(),
|
|
19
|
-
"Role Name": () => faker_1.faker.person.jobTitle(),
|
|
20
|
-
"Company Name": () => faker_1.faker.company.name(),
|
|
21
|
-
"Full Name": () => faker_1.faker.person.fullName(),
|
|
22
|
-
"Disposable Email": () => faker_1.faker.internet.email({ provider: "inboxkitten.com" }),
|
|
23
|
-
"ALpha Numeric": () => faker_1.faker.string.numeric(11) + "e",
|
|
24
|
-
"Lorem Word": () => faker_1.faker.lorem.sentences({ min: 1, max: 3 }),
|
|
25
|
-
Word: () => faker_1.faker.lorem.word({ length: { min: 5, max: 11 } }),
|
|
26
|
-
"Current Date": () => (0, dayjs_1.default)().format("YYYY-MM-DD"),
|
|
27
|
-
"Current Date2": () => new Date().toISOString().split("T")[0],
|
|
28
|
-
MonthsFromNow: (months) => {
|
|
29
|
-
const monthsToAdd = parseInt(months || "0", 10);
|
|
30
|
-
return (0, dayjs_1.default)().add(monthsToAdd, "month").format("YYYY-MM-DD");
|
|
31
|
-
},
|
|
32
|
-
MonthsAgo: (months) => {
|
|
33
|
-
const monthsToSubtract = parseInt(months || "0", 10);
|
|
34
|
-
return (0, dayjs_1.default)().subtract(monthsToSubtract, "month").format("YYYY-MM-DD");
|
|
35
|
-
},
|
|
36
|
-
WeeksFromNow: (weeks) => {
|
|
37
|
-
const weeksToAdd = parseInt(weeks || "0", 10);
|
|
38
|
-
return (0, dayjs_1.default)().add(weeksToAdd, "week").format("YYYY-MM-DD");
|
|
39
|
-
},
|
|
40
|
-
WeeksAgo: (weeks) => {
|
|
41
|
-
const weeksToSubtract = parseInt(weeks || "0", 10);
|
|
42
|
-
return (0, dayjs_1.default)().subtract(weeksToSubtract, "week").format("YYYY-MM-DD");
|
|
43
|
-
},
|
|
44
|
-
DaysFromNow: (days) => {
|
|
45
|
-
const daysToAdd = parseInt(days || "0", 10);
|
|
46
|
-
return (0, dayjs_1.default)().add(daysToAdd, "day").format("YYYY-MM-DD");
|
|
47
|
-
},
|
|
48
|
-
DaysAgo: (days) => {
|
|
49
|
-
const daysToSubtract = parseInt(days || "0", 10);
|
|
50
|
-
return (0, dayjs_1.default)().subtract(daysToSubtract, "day").format("YYYY-MM-DD");
|
|
51
|
-
},
|
|
52
|
-
};
|
|
53
|
-
function evaluateFaker(value) {
|
|
54
|
-
const [key, param] = value.split(":");
|
|
55
|
-
const fn = fakerMapping[key];
|
|
56
|
-
if (typeof fn === "function") {
|
|
57
|
-
return fn(param);
|
|
58
|
-
}
|
|
59
|
-
return value; // fallback to raw value if not mapped
|
|
60
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
-
};
|
|
16
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./fakerUtils"), exports);
|
|
18
|
-
__exportStar(require("./optionsUtils"), exports);
|
|
19
|
-
__exportStar(require("./resolveUtils"), exports);
|
|
20
|
-
__exportStar(require("./sessionUtils"), exports);
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { DataTable } from "@cucumber/cucumber";
|
|
2
|
-
import type { Locator } from "@playwright/test";
|
|
3
|
-
type ClickOptions = Parameters<Locator["click"]>[0];
|
|
4
|
-
type DblClickOptions = Parameters<Locator["dblclick"]>[0];
|
|
5
|
-
type HoverOptions = Parameters<Locator["hover"]>[0];
|
|
6
|
-
type FillOptions = Parameters<Locator["fill"]>[1];
|
|
7
|
-
type TypeOptions = Parameters<Locator["type"]>[1];
|
|
8
|
-
type CheckOptions = Parameters<Locator["check"]>[0];
|
|
9
|
-
type UncheckOptions = Parameters<Locator["uncheck"]>[0];
|
|
10
|
-
type SelectOptionOptions = Parameters<Locator["selectOption"]>[1];
|
|
11
|
-
export declare function parseClickOptions(table?: DataTable): Partial<ClickOptions>;
|
|
12
|
-
export declare function parseDblClickOptions(table?: DataTable): Partial<DblClickOptions>;
|
|
13
|
-
export declare function parseHoverOptions(table?: DataTable): Partial<HoverOptions>;
|
|
14
|
-
export declare function parseTypeOptions(table?: DataTable): Partial<TypeOptions>;
|
|
15
|
-
export declare function parseFillOptions(table?: DataTable): Partial<FillOptions>;
|
|
16
|
-
export declare function parseCheckOptions(table?: DataTable): Partial<CheckOptions>;
|
|
17
|
-
export declare function parseUncheckOptions(table?: DataTable): Partial<UncheckOptions>;
|
|
18
|
-
export declare function parseSelectOptions(table?: DataTable): Partial<SelectOptionOptions>;
|
|
19
|
-
export declare function parseGenericOptions(table?: DataTable): Record<string, any>;
|
|
20
|
-
export declare function parseExpectOptions(table?: DataTable): {
|
|
21
|
-
timeout?: number;
|
|
22
|
-
log?: boolean;
|
|
23
|
-
};
|
|
24
|
-
export {};
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.parseClickOptions = parseClickOptions;
|
|
4
|
-
exports.parseDblClickOptions = parseDblClickOptions;
|
|
5
|
-
exports.parseHoverOptions = parseHoverOptions;
|
|
6
|
-
exports.parseTypeOptions = parseTypeOptions;
|
|
7
|
-
exports.parseFillOptions = parseFillOptions;
|
|
8
|
-
exports.parseCheckOptions = parseCheckOptions;
|
|
9
|
-
exports.parseUncheckOptions = parseUncheckOptions;
|
|
10
|
-
exports.parseSelectOptions = parseSelectOptions;
|
|
11
|
-
exports.parseGenericOptions = parseGenericOptions;
|
|
12
|
-
exports.parseExpectOptions = parseExpectOptions;
|
|
13
|
-
function parseClickOptions(table) {
|
|
14
|
-
return parseGenericOptions(table);
|
|
15
|
-
}
|
|
16
|
-
function parseDblClickOptions(table) {
|
|
17
|
-
return parseGenericOptions(table);
|
|
18
|
-
}
|
|
19
|
-
function parseHoverOptions(table) {
|
|
20
|
-
return parseGenericOptions(table);
|
|
21
|
-
}
|
|
22
|
-
function parseTypeOptions(table) {
|
|
23
|
-
return parseGenericOptions(table);
|
|
24
|
-
}
|
|
25
|
-
function parseFillOptions(table) {
|
|
26
|
-
return parseGenericOptions(table);
|
|
27
|
-
}
|
|
28
|
-
function parseCheckOptions(table) {
|
|
29
|
-
return parseGenericOptions(table);
|
|
30
|
-
}
|
|
31
|
-
function parseUncheckOptions(table) {
|
|
32
|
-
return parseGenericOptions(table);
|
|
33
|
-
}
|
|
34
|
-
function parseSelectOptions(table) {
|
|
35
|
-
return parseGenericOptions(table);
|
|
36
|
-
}
|
|
37
|
-
function parseGenericOptions(table) {
|
|
38
|
-
if (!table)
|
|
39
|
-
return {};
|
|
40
|
-
const options = {};
|
|
41
|
-
const rows = table.raw();
|
|
42
|
-
for (const [key, value] of rows) {
|
|
43
|
-
switch (key) {
|
|
44
|
-
case "timeout":
|
|
45
|
-
case "delay":
|
|
46
|
-
case "clickCount":
|
|
47
|
-
options[key] = Number(value);
|
|
48
|
-
break;
|
|
49
|
-
case "force":
|
|
50
|
-
case "noWaitAfter":
|
|
51
|
-
case "strict":
|
|
52
|
-
case "trial":
|
|
53
|
-
options[key] = value === "true";
|
|
54
|
-
break;
|
|
55
|
-
case "modifiers":
|
|
56
|
-
options.modifiers = value.split(",").map((v) => v.trim());
|
|
57
|
-
break;
|
|
58
|
-
case "button":
|
|
59
|
-
if (["left", "middle", "right"].includes(value)) {
|
|
60
|
-
options.button = value;
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
throw new Error(`Invalid button option: "${value}"`);
|
|
64
|
-
}
|
|
65
|
-
break;
|
|
66
|
-
case "position":
|
|
67
|
-
const [x, y] = value.split(",").map((n) => Number(n.trim()));
|
|
68
|
-
if (isNaN(x) || isNaN(y)) {
|
|
69
|
-
throw new Error(`Invalid position format: "${value}"`);
|
|
70
|
-
}
|
|
71
|
-
options.position = { x, y };
|
|
72
|
-
break;
|
|
73
|
-
default:
|
|
74
|
-
console.warn(`[⚠️ parseGenericOptions] Unknown option "${key}"`);
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return options;
|
|
79
|
-
}
|
|
80
|
-
function parseExpectOptions(table) {
|
|
81
|
-
if (!table)
|
|
82
|
-
return {};
|
|
83
|
-
const obj = Object.fromEntries(table.rows());
|
|
84
|
-
return {
|
|
85
|
-
timeout: obj.timeout ? Number(obj.timeout) : undefined,
|
|
86
|
-
log: obj.log === "true",
|
|
87
|
-
};
|
|
88
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { CustomWorld } from "../world";
|
|
2
|
-
export declare function resolveValue(input: string): string;
|
|
3
|
-
export declare function resolveLoginValue(raw: string, world: CustomWorld): string | undefined;
|
|
4
|
-
export declare function deriveSessionName(emailOrUser: string, fallback?: string): string;
|
|
5
|
-
export declare function resolveSessionPath(world: CustomWorld, name: string): string;
|
|
6
|
-
export declare function normalizeDeviceName(name: string): string;
|