playwright-cucumber-ts-steps 0.1.2 → 0.1.4
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 +174 -39
- package/{dist → lib}/actions/clickSteps.js +10 -3
- package/{dist → lib}/actions/debugSteps.js +2 -5
- package/{dist → lib}/actions/elementFindSteps.js +15 -18
- package/lib/actions/fillFormSteps.js +130 -0
- package/{dist → lib}/actions/inputSteps.js +9 -76
- package/{dist → lib}/actions/interceptionSteps.js +8 -0
- package/{dist → lib}/actions/miscSteps.js +60 -12
- package/{dist → lib}/actions/storageSteps.js +26 -4
- package/{dist → lib}/assertions/buttonAndTextVisibilitySteps.js +3 -23
- package/{dist → lib}/assertions/elementSteps.js +7 -1
- package/{dist → lib}/assertions/locationSteps.js +16 -0
- package/{dist → lib}/assertions/semanticSteps.js +16 -3
- package/{dist → lib}/assertions/visualSteps.js +9 -5
- package/lib/custom_setups/loginHooks.js +113 -0
- package/lib/helpers/hooks.js +210 -0
- package/{dist → lib}/helpers/utils/index.d.ts +1 -0
- package/{dist → lib}/helpers/utils/index.js +1 -0
- package/{dist → lib}/helpers/utils/optionsUtils.d.ts +5 -0
- package/{dist → lib}/helpers/utils/optionsUtils.js +12 -3
- package/{dist → lib}/helpers/utils/resolveUtils.d.ts +2 -0
- package/{dist → lib}/helpers/utils/resolveUtils.js +13 -3
- package/lib/helpers/utils/sessionUtils.d.ts +3 -0
- package/lib/helpers/utils/sessionUtils.js +40 -0
- package/{dist → lib}/helpers/world.d.ts +13 -0
- package/{dist → lib}/helpers/world.js +17 -10
- package/lib/iframes/frames.d.ts +1 -0
- package/{dist → lib}/index.d.ts +1 -1
- package/{dist → lib}/index.js +1 -1
- package/{dist → lib}/register.js +1 -5
- package/package.json +45 -23
- package/dist/custom_setups/globalLogin.d.ts +0 -2
- package/dist/custom_setups/globalLogin.js +0 -25
- package/dist/custom_setups/loginHooks.js +0 -141
- package/dist/helpers/hooks.js +0 -184
- package/{dist → lib}/actions/clickSteps.d.ts +0 -0
- package/{dist → lib}/actions/cookieSteps.d.ts +0 -0
- package/{dist → lib}/actions/cookieSteps.js +0 -0
- package/{dist → lib}/actions/debugSteps.d.ts +0 -0
- package/{dist → lib}/actions/elementFindSteps.d.ts +0 -0
- package/{dist/actions/inputSteps.d.ts → lib/actions/fillFormSteps.d.ts} +0 -0
- package/{dist/actions/interceptionSteps.d.ts → lib/actions/inputSteps.d.ts} +0 -0
- package/{dist/actions/miscSteps.d.ts → lib/actions/interceptionSteps.d.ts} +0 -0
- package/{dist/actions/mouseSteps.d.ts → lib/actions/miscSteps.d.ts} +0 -0
- package/{dist/actions/scrollSteps.d.ts → lib/actions/mouseSteps.d.ts} +0 -0
- package/{dist → lib}/actions/mouseSteps.js +0 -0
- package/{dist/actions/storageSteps.d.ts → lib/actions/scrollSteps.d.ts} +0 -0
- package/{dist → lib}/actions/scrollSteps.js +0 -0
- package/{dist/assertions → lib/actions}/storageSteps.d.ts +0 -0
- package/{dist → lib}/assertions/buttonAndTextVisibilitySteps.d.ts +0 -0
- package/{dist → lib}/assertions/cookieSteps.d.ts +0 -0
- package/{dist → lib}/assertions/cookieSteps.js +0 -0
- package/{dist → lib}/assertions/elementSteps.d.ts +0 -0
- package/{dist → lib}/assertions/formInputSteps.d.ts +0 -0
- package/{dist → lib}/assertions/formInputSteps.js +0 -0
- package/{dist → lib}/assertions/interceptionRequestsSteps.d.ts +0 -0
- package/{dist → lib}/assertions/interceptionRequestsSteps.js +0 -0
- package/{dist → lib}/assertions/locationSteps.d.ts +0 -0
- package/{dist → lib}/assertions/roleTestIdSteps.d.ts +0 -0
- package/{dist → lib}/assertions/roleTestIdSteps.js +0 -0
- package/{dist → lib}/assertions/semanticSteps.d.ts +0 -0
- package/{dist/assertions/visualSteps.d.ts → lib/assertions/storageSteps.d.ts} +0 -0
- package/{dist → lib}/assertions/storageSteps.js +1 -1
- /package/{dist/custom_setups/loginHooks.d.ts → lib/assertions/visualSteps.d.ts} +0 -0
- /package/{dist/helpers/hooks.d.ts → lib/custom_setups/loginHooks.d.ts} +0 -0
- /package/{dist → lib}/helpers/checkPeerDeps.d.ts +0 -0
- /package/{dist → lib}/helpers/checkPeerDeps.js +0 -0
- /package/{dist → lib}/helpers/compareSnapshots.d.ts +0 -0
- /package/{dist → lib}/helpers/compareSnapshots.js +0 -0
- /package/{dist/iframes/frames.d.ts → lib/helpers/hooks.d.ts} +0 -0
- /package/{dist → lib}/helpers/utils/fakerUtils.d.ts +0 -0
- /package/{dist → lib}/helpers/utils/fakerUtils.js +0 -0
- /package/{dist → lib}/iframes/frames.js +0 -0
- /package/{dist → lib}/register.d.ts +0 -0
|
@@ -17,3 +17,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
__exportStar(require("./fakerUtils"), exports);
|
|
18
18
|
__exportStar(require("./optionsUtils"), exports);
|
|
19
19
|
__exportStar(require("./resolveUtils"), exports);
|
|
20
|
+
__exportStar(require("./sessionUtils"), exports);
|
|
@@ -16,4 +16,9 @@ export declare function parseFillOptions(table?: DataTable): Partial<FillOptions
|
|
|
16
16
|
export declare function parseCheckOptions(table?: DataTable): Partial<CheckOptions>;
|
|
17
17
|
export declare function parseUncheckOptions(table?: DataTable): Partial<UncheckOptions>;
|
|
18
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
|
+
};
|
|
19
24
|
export {};
|
|
@@ -8,6 +8,8 @@ exports.parseFillOptions = parseFillOptions;
|
|
|
8
8
|
exports.parseCheckOptions = parseCheckOptions;
|
|
9
9
|
exports.parseUncheckOptions = parseUncheckOptions;
|
|
10
10
|
exports.parseSelectOptions = parseSelectOptions;
|
|
11
|
+
exports.parseGenericOptions = parseGenericOptions;
|
|
12
|
+
exports.parseExpectOptions = parseExpectOptions;
|
|
11
13
|
function parseClickOptions(table) {
|
|
12
14
|
return parseGenericOptions(table);
|
|
13
15
|
}
|
|
@@ -51,9 +53,7 @@ function parseGenericOptions(table) {
|
|
|
51
53
|
options[key] = value === "true";
|
|
52
54
|
break;
|
|
53
55
|
case "modifiers":
|
|
54
|
-
options.modifiers = value
|
|
55
|
-
.split(",")
|
|
56
|
-
.map((v) => v.trim());
|
|
56
|
+
options.modifiers = value.split(",").map((v) => v.trim());
|
|
57
57
|
break;
|
|
58
58
|
case "button":
|
|
59
59
|
if (["left", "middle", "right"].includes(value)) {
|
|
@@ -77,3 +77,12 @@ function parseGenericOptions(table) {
|
|
|
77
77
|
}
|
|
78
78
|
return options;
|
|
79
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
|
+
}
|
|
@@ -2,3 +2,5 @@ import type { CustomWorld } from "../world";
|
|
|
2
2
|
export declare function resolveValue(input: string): string;
|
|
3
3
|
export declare function resolveLoginValue(raw: string, world: CustomWorld): string | undefined;
|
|
4
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;
|
|
@@ -6,8 +6,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.resolveValue = resolveValue;
|
|
7
7
|
exports.resolveLoginValue = resolveLoginValue;
|
|
8
8
|
exports.deriveSessionName = deriveSessionName;
|
|
9
|
+
exports.resolveSessionPath = resolveSessionPath;
|
|
10
|
+
exports.normalizeDeviceName = normalizeDeviceName;
|
|
11
|
+
// src/helpers/utils/resolveUtils.ts
|
|
9
12
|
const fs_1 = __importDefault(require("fs"));
|
|
10
13
|
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const test_1 = require("@playwright/test");
|
|
11
15
|
// Dynamic resolver
|
|
12
16
|
function resolveValue(input) {
|
|
13
17
|
// Uppercase = environment variable (e.g. TEST_USER)
|
|
@@ -55,8 +59,14 @@ function resolveLoginValue(raw, world) {
|
|
|
55
59
|
function deriveSessionName(emailOrUser, fallback = "default") {
|
|
56
60
|
if (!emailOrUser)
|
|
57
61
|
return `${fallback}User`;
|
|
58
|
-
const base = emailOrUser.includes("@")
|
|
59
|
-
? emailOrUser.split("@")[0]
|
|
60
|
-
: emailOrUser;
|
|
62
|
+
const base = emailOrUser.includes("@") ? emailOrUser.split("@")[0] : emailOrUser;
|
|
61
63
|
return `${base}User`;
|
|
62
64
|
}
|
|
65
|
+
function resolveSessionPath(world, name) {
|
|
66
|
+
const dir = path_1.default.resolve(world.data.artifactDir || "test-artifacts", "auth-cookies");
|
|
67
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
68
|
+
return path_1.default.resolve(dir, name.endsWith(".json") ? name : `${name}.json`);
|
|
69
|
+
}
|
|
70
|
+
function normalizeDeviceName(name) {
|
|
71
|
+
return (Object.keys(test_1.devices).find((device) => device.toLowerCase().replace(/\s+/g, "") === name.toLowerCase().replace(/[-_\s]+/g, "")) || "");
|
|
72
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
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.injectStorage = injectStorage;
|
|
7
|
+
exports.saveSessionData = saveSessionData;
|
|
8
|
+
//sessionUtils.ts
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
async function injectStorage(world, type, key, value) {
|
|
12
|
+
const page = world.page;
|
|
13
|
+
if (type === "localStorage") {
|
|
14
|
+
await page.evaluate(([k, v]) => localStorage.setItem(k, v), [key, value]);
|
|
15
|
+
}
|
|
16
|
+
else if (type === "sessionStorage") {
|
|
17
|
+
await page.evaluate(([k, v]) => sessionStorage.setItem(k, v), [key, value]);
|
|
18
|
+
}
|
|
19
|
+
else if (type === "cookie") {
|
|
20
|
+
await page.context.addCookies([
|
|
21
|
+
{
|
|
22
|
+
name: key,
|
|
23
|
+
value,
|
|
24
|
+
domain: new URL(page.url()).hostname,
|
|
25
|
+
path: "/",
|
|
26
|
+
httpOnly: false,
|
|
27
|
+
secure: true,
|
|
28
|
+
sameSite: "Lax",
|
|
29
|
+
},
|
|
30
|
+
]);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function saveSessionData(context, file) {
|
|
34
|
+
const storageState = await context.storageState();
|
|
35
|
+
const outDir = "test-artifacts/auth-cookies";
|
|
36
|
+
const outPath = path_1.default.resolve(outDir, file);
|
|
37
|
+
fs_1.default.mkdirSync(outDir, { recursive: true });
|
|
38
|
+
fs_1.default.writeFileSync(outPath, JSON.stringify(storageState, null, 2));
|
|
39
|
+
console.log(`💾 Saved session to ${outPath}`);
|
|
40
|
+
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { World, ITestCaseHookParameter } from "@cucumber/cucumber";
|
|
2
2
|
import { Browser, Page, BrowserContext, Locator, FrameLocator } from "@playwright/test";
|
|
3
|
+
export interface TestConfig {
|
|
4
|
+
enableScreenshots: boolean;
|
|
5
|
+
enableVisualTest: boolean;
|
|
6
|
+
artifactDir: string;
|
|
7
|
+
}
|
|
3
8
|
export declare class CustomWorld extends World {
|
|
4
9
|
browser: Browser;
|
|
5
10
|
context: BrowserContext;
|
|
@@ -10,8 +15,16 @@ export declare class CustomWorld extends World {
|
|
|
10
15
|
data: Record<string, any>;
|
|
11
16
|
logs: string[];
|
|
12
17
|
testName?: string;
|
|
18
|
+
config: TestConfig;
|
|
13
19
|
init(testInfo?: ITestCaseHookParameter): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Returns the current interaction scope: either the main page or active frame.
|
|
22
|
+
*/
|
|
14
23
|
getScope(): Page | FrameLocator;
|
|
24
|
+
/**
|
|
25
|
+
* Returns a Locator scoped to the current iframe or page.
|
|
26
|
+
*/
|
|
27
|
+
getLocator(selector: string): Locator;
|
|
15
28
|
exitIframe(): void;
|
|
16
29
|
log: (message: string) => void;
|
|
17
30
|
cleanup(testInfo?: ITestCaseHookParameter): Promise<void>;
|
|
@@ -34,11 +34,10 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.CustomWorld = void 0;
|
|
37
|
-
// support/world.ts
|
|
38
37
|
const cucumber_1 = require("@cucumber/cucumber");
|
|
39
38
|
const test_1 = require("@playwright/test");
|
|
40
|
-
const playwright_1 = require("playwright");
|
|
41
39
|
const dotenv = __importStar(require("dotenv"));
|
|
40
|
+
const playwright_1 = require("playwright");
|
|
42
41
|
dotenv.config();
|
|
43
42
|
const isHeadless = process.env.HEADLESS !== "false";
|
|
44
43
|
const slowMo = process.env.SLOWMO ? Number(process.env.SLOWMO) : 0;
|
|
@@ -47,6 +46,11 @@ class CustomWorld extends cucumber_1.World {
|
|
|
47
46
|
super(...arguments);
|
|
48
47
|
this.data = {};
|
|
49
48
|
this.logs = [];
|
|
49
|
+
this.config = {
|
|
50
|
+
enableScreenshots: true,
|
|
51
|
+
enableVisualTest: false,
|
|
52
|
+
artifactDir: "test-artifacts",
|
|
53
|
+
};
|
|
50
54
|
this.log = (message) => {
|
|
51
55
|
this.logs.push(message);
|
|
52
56
|
console.log(`[LOG] ${message}`);
|
|
@@ -58,21 +62,30 @@ class CustomWorld extends cucumber_1.World {
|
|
|
58
62
|
this.browser = await playwright_1.chromium.launch({ headless: isHeadless, slowMo });
|
|
59
63
|
this.context = await this.browser.newContext({
|
|
60
64
|
...(device || {}),
|
|
61
|
-
recordVideo: { dir:
|
|
65
|
+
recordVideo: { dir: `${this.config.artifactDir}/videos` },
|
|
62
66
|
});
|
|
63
67
|
this.page = await this.context.newPage();
|
|
64
68
|
this.testName = testInfo?.pickle.name;
|
|
65
69
|
this.log(`🧪 Initialized context${isMobile ? " (mobile)" : ""}`);
|
|
66
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Returns the current interaction scope: either the main page or active frame.
|
|
73
|
+
*/
|
|
67
74
|
getScope() {
|
|
68
75
|
return this.frame ?? this.page;
|
|
69
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Returns a Locator scoped to the current iframe or page.
|
|
79
|
+
*/
|
|
80
|
+
getLocator(selector) {
|
|
81
|
+
return this.getScope().locator(selector);
|
|
82
|
+
}
|
|
70
83
|
exitIframe() {
|
|
71
84
|
this.frame = undefined;
|
|
72
85
|
this.log("⬅️ Exited iframe, scope is now main page");
|
|
73
86
|
}
|
|
74
87
|
async cleanup(testInfo) {
|
|
75
|
-
|
|
88
|
+
testInfo?.result?.status === "FAILED";
|
|
76
89
|
try {
|
|
77
90
|
await this.page?.close();
|
|
78
91
|
}
|
|
@@ -85,12 +98,6 @@ class CustomWorld extends cucumber_1.World {
|
|
|
85
98
|
catch (err) {
|
|
86
99
|
this.log(`⚠️ Error closing context: ${err.message}`);
|
|
87
100
|
}
|
|
88
|
-
try {
|
|
89
|
-
await this.browser?.close();
|
|
90
|
-
}
|
|
91
|
-
catch (err) {
|
|
92
|
-
this.log(`⚠️ Error closing browser: ${err.message}`);
|
|
93
|
-
}
|
|
94
101
|
}
|
|
95
102
|
}
|
|
96
103
|
exports.CustomWorld = CustomWorld;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/{dist → lib}/index.d.ts
RENAMED
|
@@ -2,6 +2,7 @@ export * from "./actions/clickSteps";
|
|
|
2
2
|
export * from "./actions/cookieSteps";
|
|
3
3
|
export * from "./actions/debugSteps";
|
|
4
4
|
export * from "./actions/elementFindSteps";
|
|
5
|
+
export * from "./actions/fillFormSteps";
|
|
5
6
|
export * from "./actions/inputSteps";
|
|
6
7
|
export * from "./actions/interceptionSteps";
|
|
7
8
|
export * from "./actions/miscSteps";
|
|
@@ -19,7 +20,6 @@ export * from "./assertions/semanticSteps";
|
|
|
19
20
|
export * from "./assertions/storageSteps";
|
|
20
21
|
export * from "./assertions/visualSteps";
|
|
21
22
|
export * from "./iframes/frames";
|
|
22
|
-
export * from "./custom_setups/globalLogin";
|
|
23
23
|
export * from "./custom_setups/loginHooks";
|
|
24
24
|
export * from "./helpers/compareSnapshots";
|
|
25
25
|
export * from "./helpers/hooks";
|
package/{dist → lib}/index.js
RENAMED
|
@@ -19,6 +19,7 @@ __exportStar(require("./actions/clickSteps"), exports);
|
|
|
19
19
|
__exportStar(require("./actions/cookieSteps"), exports);
|
|
20
20
|
__exportStar(require("./actions/debugSteps"), exports);
|
|
21
21
|
__exportStar(require("./actions/elementFindSteps"), exports);
|
|
22
|
+
__exportStar(require("./actions/fillFormSteps"), exports);
|
|
22
23
|
__exportStar(require("./actions/inputSteps"), exports);
|
|
23
24
|
__exportStar(require("./actions/interceptionSteps"), exports);
|
|
24
25
|
__exportStar(require("./actions/miscSteps"), exports);
|
|
@@ -39,7 +40,6 @@ __exportStar(require("./assertions/visualSteps"), exports);
|
|
|
39
40
|
// Iframes
|
|
40
41
|
__exportStar(require("./iframes/frames"), exports);
|
|
41
42
|
// Setup (custom hooks, login, etc.)
|
|
42
|
-
__exportStar(require("./custom_setups/globalLogin"), exports);
|
|
43
43
|
__exportStar(require("./custom_setups/loginHooks"), exports);
|
|
44
44
|
// Core helpers and utilities
|
|
45
45
|
__exportStar(require("./helpers/compareSnapshots"), exports);
|
package/{dist → lib}/register.js
RENAMED
|
@@ -2,9 +2,5 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
// src/register.ts
|
|
4
4
|
const checkPeerDeps_1 = require("./helpers/checkPeerDeps");
|
|
5
|
-
(0, checkPeerDeps_1.checkPeerDependencies)([
|
|
6
|
-
"@cucumber/cucumber",
|
|
7
|
-
"@playwright/test",
|
|
8
|
-
"@faker-js/faker",
|
|
9
|
-
]);
|
|
5
|
+
(0, checkPeerDeps_1.checkPeerDependencies)(["@cucumber/cucumber", "@playwright/test", "@faker-js/faker"]);
|
|
10
6
|
require("./index");
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "playwright-cucumber-ts-steps",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "A collection of reusable Playwright step definitions for Cucumber in TypeScript, designed to streamline end-to-end testing across web, API, and mobile applications.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
8
11
|
"exports": {
|
|
9
12
|
".": {
|
|
10
13
|
"import": "./dist/index.js"
|
|
@@ -22,7 +25,27 @@
|
|
|
22
25
|
}
|
|
23
26
|
},
|
|
24
27
|
"scripts": {
|
|
25
|
-
"build": "tsc"
|
|
28
|
+
"build": "tsc",
|
|
29
|
+
"docs": "typedoc src",
|
|
30
|
+
"prepare": "husky",
|
|
31
|
+
"lint": "eslint . --ext .ts",
|
|
32
|
+
"lint:fix": "eslint . --ext .ts --fix",
|
|
33
|
+
"lint:tsc": "tsc --noEmit",
|
|
34
|
+
"format": "prettier --write .",
|
|
35
|
+
"clean": "rm -rf playwright/report",
|
|
36
|
+
"test": "npm run clean && cucumber-js --config playwright/config/cucumber.js",
|
|
37
|
+
"commitlint": "commitlint --edit"
|
|
38
|
+
},
|
|
39
|
+
"lint-staged": {
|
|
40
|
+
"*.{js,ts,tsx,json,md}": [
|
|
41
|
+
"eslint --fix",
|
|
42
|
+
"prettier --write"
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"commitlint": {
|
|
46
|
+
"extends": [
|
|
47
|
+
"@commitlint/config-conventional"
|
|
48
|
+
]
|
|
26
49
|
},
|
|
27
50
|
"repository": {
|
|
28
51
|
"type": "git",
|
|
@@ -46,39 +69,38 @@
|
|
|
46
69
|
"mobile",
|
|
47
70
|
"visual-testing"
|
|
48
71
|
],
|
|
49
|
-
"author": "qaPaschalE",
|
|
72
|
+
"author": "qaPaschalE <paschal.enyimiri@gmail.com>",
|
|
50
73
|
"license": "MIT",
|
|
51
74
|
"bugs": {
|
|
52
75
|
"url": "https://github.com/qaPaschalE/playwright-cucumber-ts-steps/issues"
|
|
53
76
|
},
|
|
54
77
|
"homepage": "https://github.com/qaPaschalE/playwright-cucumber-ts-steps#readme",
|
|
55
78
|
"peerDependencies": {
|
|
56
|
-
"@cucumber/cucumber": "
|
|
57
|
-
"@playwright/test": "
|
|
58
|
-
"@faker-js/faker": "^9.8.0"
|
|
59
|
-
},
|
|
60
|
-
"peerDependenciesMeta": {
|
|
61
|
-
"@cucumber/cucumber": {
|
|
62
|
-
"optional": false
|
|
63
|
-
},
|
|
64
|
-
"@playwright/test": {
|
|
65
|
-
"optional": false
|
|
66
|
-
},
|
|
67
|
-
"@faker-js/faker": {
|
|
68
|
-
"optional": false
|
|
69
|
-
}
|
|
79
|
+
"@cucumber/cucumber": "*",
|
|
80
|
+
"@playwright/test": "*"
|
|
70
81
|
},
|
|
71
|
-
"
|
|
72
|
-
"@cucumber/
|
|
82
|
+
"dependencies": {
|
|
83
|
+
"@cucumber/tag-expressions": "^6.2.0",
|
|
73
84
|
"@faker-js/faker": "^9.8.0",
|
|
74
|
-
"@playwright/test": "^1.53.0",
|
|
75
|
-
"@types/pngjs": "^6.0.5",
|
|
76
85
|
"dayjs": "^1.11.13",
|
|
77
86
|
"dotenv-cli": "^8.0.0",
|
|
87
|
+
"glob": "^11.0.3",
|
|
78
88
|
"parse": "^6.1.1",
|
|
79
89
|
"pixelmatch": "^7.1.0",
|
|
80
|
-
"pngjs": "^7.0.0"
|
|
90
|
+
"pngjs": "^7.0.0"
|
|
91
|
+
},
|
|
92
|
+
"devDependencies": {
|
|
93
|
+
"@commitlint/cli": "^19.8.1",
|
|
94
|
+
"@commitlint/config-conventional": "^19.8.1",
|
|
95
|
+
"@types/pngjs": "^6.0.5",
|
|
96
|
+
"eslint": "^9.29.0",
|
|
97
|
+
"eslint-config-prettier": "^10.1.5",
|
|
98
|
+
"eslint-plugin-import": "^2.32.0",
|
|
99
|
+
"husky": "^9.1.7",
|
|
100
|
+
"lint-staged": "^16.1.2",
|
|
101
|
+
"prettier": "^3.5.3",
|
|
81
102
|
"ts-node": "^10.9.2",
|
|
82
|
-
"typescript": "^5.8.3"
|
|
103
|
+
"typescript": "^5.8.3",
|
|
104
|
+
"typescript-eslint": "^8.34.1"
|
|
83
105
|
}
|
|
84
106
|
}
|
|
@@ -1,25 +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
|
-
// global-setup.ts
|
|
7
|
-
const test_1 = require("@playwright/test");
|
|
8
|
-
const path_1 = __importDefault(require("path"));
|
|
9
|
-
async function globalSetup() {
|
|
10
|
-
const browser = await test_1.chromium.launch();
|
|
11
|
-
const page = await browser.newPage();
|
|
12
|
-
// Navigate to login and log in
|
|
13
|
-
await page.goto(process.env.PLAYWRIGHT_BASE_URL || "http://demoqa.com");
|
|
14
|
-
await page.getByPlaceholder("Email").fill("user@example.com");
|
|
15
|
-
await page.getByPlaceholder("Password").fill("SuperSecret123");
|
|
16
|
-
await page.getByRole("button", { name: "Login" }).click();
|
|
17
|
-
// Wait for navigation or some indicator that login is complete
|
|
18
|
-
await page.waitForURL("**/dashboard");
|
|
19
|
-
// Save session to file
|
|
20
|
-
await page
|
|
21
|
-
.context()
|
|
22
|
-
.storageState({ path: path_1.default.resolve(__dirname, "storageState.json") });
|
|
23
|
-
await browser.close();
|
|
24
|
-
}
|
|
25
|
-
exports.default = globalSetup;
|
|
@@ -1,141 +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
|
-
// e2e/step_definitions/auth/loginHooks.ts
|
|
7
|
-
const cucumber_1 = require("@cucumber/cucumber");
|
|
8
|
-
const test_1 = require("@playwright/test");
|
|
9
|
-
const path_1 = __importDefault(require("path"));
|
|
10
|
-
const resolveUtils_1 = require("../helpers/utils/resolveUtils");
|
|
11
|
-
const fs_1 = __importDefault(require("fs"));
|
|
12
|
-
const console_1 = require("console");
|
|
13
|
-
async function tryRestoreSession(world, sessionName) {
|
|
14
|
-
const storagePath = path_1.default.resolve("e2e/support/helper/auth", `${sessionName}.json`);
|
|
15
|
-
if (fs_1.default.existsSync(storagePath)) {
|
|
16
|
-
await world.context?.addCookies([]);
|
|
17
|
-
await world.page.context().addInitScript(() => {
|
|
18
|
-
// preload logic if needed
|
|
19
|
-
});
|
|
20
|
-
await world.page.context().storageState({ path: storagePath });
|
|
21
|
-
world.log(`Session for ${sessionName} restored from file.`);
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
async function loginAndSaveSession(world, email, password, sessionName) {
|
|
27
|
-
const baseUrl = process.env.BASE_URL;
|
|
28
|
-
if (!baseUrl)
|
|
29
|
-
throw new Error("Missing BASE_URL in environment");
|
|
30
|
-
await pageLogin(world, baseUrl, email, password);
|
|
31
|
-
const sessionFile = path_1.default.resolve("e2e/support/helper/auth", `${sessionName}.json`);
|
|
32
|
-
await world.page.context().storageState({ path: sessionFile });
|
|
33
|
-
world.log(`Session saved to ${sessionFile}`);
|
|
34
|
-
}
|
|
35
|
-
const loginStep = async function (user, pass) {
|
|
36
|
-
const email = (0, resolveUtils_1.resolveValue)(user);
|
|
37
|
-
const password = (0, resolveUtils_1.resolveValue)(pass);
|
|
38
|
-
const sessionName = (0, resolveUtils_1.deriveSessionName)(email);
|
|
39
|
-
const restored = await tryRestoreSession(this, sessionName);
|
|
40
|
-
if (!restored) {
|
|
41
|
-
await loginAndSaveSession(this, email, password, sessionName);
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
(0, cucumber_1.When)("I am logged out", async function () {
|
|
45
|
-
const { page } = this;
|
|
46
|
-
await page.goto("/");
|
|
47
|
-
const url = page.url();
|
|
48
|
-
if (!url.includes("/login")) {
|
|
49
|
-
await page.locator("//div[@id=':rti:']//*[name()='svg']").last().click();
|
|
50
|
-
await page.getByText("Logout").click();
|
|
51
|
-
await (0, test_1.expect)(page.getByText("Log in")).toBeVisible();
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
(0, cucumber_1.When)("I login as a default user", async function () {
|
|
55
|
-
const baseUrl = process.env.BASE_URL;
|
|
56
|
-
const email = process.env.USER_EMAIL;
|
|
57
|
-
const password = process.env.USER_PASSWORD;
|
|
58
|
-
if (!baseUrl || !email || !password) {
|
|
59
|
-
console.warn("Missing base URL or credentials");
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
await pageLogin(this, baseUrl, email, password);
|
|
63
|
-
});
|
|
64
|
-
(0, cucumber_1.When)("I login with user {string} and password {string}", loginStep);
|
|
65
|
-
// Given("I login with user {string} and password {string}", loginStep);
|
|
66
|
-
// Special alias step
|
|
67
|
-
(0, cucumber_1.When)("I login as {string} user", async function (alias) {
|
|
68
|
-
const sessionFile = path_1.default.resolve("storage", `${alias}User.json`);
|
|
69
|
-
const email = process.env.USER_EMAIL;
|
|
70
|
-
const password = process.env.USER_PASSWORD;
|
|
71
|
-
const baseUrl = process.env.BASE_URL;
|
|
72
|
-
if (!email || !password) {
|
|
73
|
-
throw new Error("USER_EMAIL or USER_PASSWORD not set in .env");
|
|
74
|
-
}
|
|
75
|
-
if (fs_1.default.existsSync(sessionFile)) {
|
|
76
|
-
await this.context.addCookies([]); // optional reset
|
|
77
|
-
await this.context.addInitScript(() => { });
|
|
78
|
-
await this.context.storageState({ path: sessionFile });
|
|
79
|
-
this.log(`🗂️ Restored session from ${alias}User.json`);
|
|
80
|
-
// Wait for page to stabilize
|
|
81
|
-
await this.page.goto(baseUrl);
|
|
82
|
-
await this.page.waitForTimeout(1000);
|
|
83
|
-
const bodyText = await this.page.locator("body").innerText();
|
|
84
|
-
const url = this.page.url();
|
|
85
|
-
const looksLoggedOut = bodyText.match(/sign\s?in|login/i) || /login|signin/.test(url);
|
|
86
|
-
if (!looksLoggedOut) {
|
|
87
|
-
this.log("✅ Session is valid, no login required.");
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
this.log("⚠️ Session appears invalid, re-logging in.");
|
|
91
|
-
}
|
|
92
|
-
// Perform login
|
|
93
|
-
await pageLogin(this, baseUrl, email, password);
|
|
94
|
-
await this.context.storageState({ path: sessionFile });
|
|
95
|
-
this.log(`💾 Saved session to ${alias}User.json`);
|
|
96
|
-
});
|
|
97
|
-
(0, cucumber_1.When)("I perform login with:", async function (dataTable) {
|
|
98
|
-
const loginData = Object.fromEntries(dataTable.raw());
|
|
99
|
-
const email = (0, resolveUtils_1.resolveLoginValue)(loginData.email, this);
|
|
100
|
-
const password = (0, resolveUtils_1.resolveLoginValue)(loginData.password, this) ?? process.env.USER_PASSWORD;
|
|
101
|
-
if (!email)
|
|
102
|
-
throw new Error("Missing or invalid email for login");
|
|
103
|
-
if (!password)
|
|
104
|
-
throw new Error("Missing or invalid password for login");
|
|
105
|
-
this.log?.(`🔐 Logging in with: ${email}`);
|
|
106
|
-
await this.page.goto(`${process.env.BASE_URL}/login`);
|
|
107
|
-
await this.page.waitForLoadState("networkidle");
|
|
108
|
-
await this.page.fill('input[type="email"]', email);
|
|
109
|
-
await this.page.fill('input[type="password"]', password);
|
|
110
|
-
const loginButton = this.page.getByRole("button", { name: /login/i });
|
|
111
|
-
await loginButton.click();
|
|
112
|
-
await this.page.waitForLoadState("networkidle");
|
|
113
|
-
this.log?.("✅ Login successful");
|
|
114
|
-
});
|
|
115
|
-
async function pageLogin(world, baseUrl, email, password) {
|
|
116
|
-
const { page } = world;
|
|
117
|
-
await page.goto(baseUrl);
|
|
118
|
-
await page.waitForLoadState("networkidle");
|
|
119
|
-
if (page.url().includes("/login")) {
|
|
120
|
-
await page.getByPlaceholder("email").fill(email);
|
|
121
|
-
await page.getByPlaceholder("password").fill(password);
|
|
122
|
-
await page.getByRole("button", { name: "Login" }).click();
|
|
123
|
-
await page.waitForLoadState("networkidle");
|
|
124
|
-
if (await page.getByText("Select an account").isVisible()) {
|
|
125
|
-
(0, console_1.log)("Login successful, navigating to accounts page");
|
|
126
|
-
await page.goto(`${baseUrl}/indicina`);
|
|
127
|
-
}
|
|
128
|
-
await (0, test_1.expect)(page.getByText("Total unique customers")).toBeVisible({
|
|
129
|
-
timeout: 10000,
|
|
130
|
-
});
|
|
131
|
-
// ✅ Save session after successful login
|
|
132
|
-
await page.context().storageState({
|
|
133
|
-
path: path_1.default.resolve("e2e/support/helper/auth", "session.json"),
|
|
134
|
-
});
|
|
135
|
-
world.data["loggedIn"] = true;
|
|
136
|
-
world.log?.("✅ Logged in and session saved.");
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
console.log("Already logged in");
|
|
140
|
-
}
|
|
141
|
-
}
|