playwright-cucumber-ts-steps 0.0.1 → 0.0.2
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/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/index.ts +2 -2
- package/package.json +7 -1
- package/register.ts +31 -0
- package/helpers/utils/fakerUtils.ts +0 -68
- package/helpers/utils/index.ts +0 -3
- package/helpers/utils/optionsUtils.ts +0 -106
- package/helpers/utils/resolveUtils.ts +0 -69
- package/helpers/world.ts +0 -91
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export * from "./src";
|
|
2
|
-
export * from "./helpers/world";
|
|
3
|
-
export * from "./helpers/utils";
|
|
2
|
+
export * from "./src/helpers/world";
|
|
3
|
+
export * from "./src/helpers/utils";
|
package/dist/index.js
CHANGED
package/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "playwright-cucumber-ts-steps",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
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": "module",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -8,6 +8,12 @@
|
|
|
8
8
|
"scripts": {
|
|
9
9
|
"build": "tsc"
|
|
10
10
|
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
11
17
|
"repository": {
|
|
12
18
|
"type": "git",
|
|
13
19
|
"url": "git+https://github.com/qaPaschalE/playwright-cucumber-ts-steps.git"
|
package/register.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// src/register.ts
|
|
2
|
+
import "./src/helpers/world.js";
|
|
3
|
+
|
|
4
|
+
// Custom setups
|
|
5
|
+
import "./custom_setups/global-login.js";
|
|
6
|
+
import "./custom_setups/loginHooks.js";
|
|
7
|
+
|
|
8
|
+
// Actions
|
|
9
|
+
import "./actions/Interception & Requests.js";
|
|
10
|
+
import "./actions/clickSteps.js";
|
|
11
|
+
import "./actions/cookieSteps.js";
|
|
12
|
+
import "./actions/debugSteps.js";
|
|
13
|
+
import "./actions/elementFindSteps.js";
|
|
14
|
+
import "./actions/inputSteps.js";
|
|
15
|
+
import "./actions/mouseSteps.js";
|
|
16
|
+
import "./actions/miscSteps.js";
|
|
17
|
+
import "./actions/scrollSteps.js";
|
|
18
|
+
import "./actions/storageSteps.js";
|
|
19
|
+
|
|
20
|
+
// Assertions
|
|
21
|
+
import "./assertions/button_and_text_visibility.js";
|
|
22
|
+
import "./assertions/cookieSteps.js";
|
|
23
|
+
import "./assertions/elementSteps.js";
|
|
24
|
+
import "./assertions/formInputSteps.js";
|
|
25
|
+
import "./assertions/locationSteps.js";
|
|
26
|
+
import "./assertions/roleTestIdSteps.js";
|
|
27
|
+
import "./assertions/semanticSteps.js";
|
|
28
|
+
import "./assertions/storageSteps.js";
|
|
29
|
+
|
|
30
|
+
// Iframes
|
|
31
|
+
import "./iframes/frames.js";
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { faker } from "@faker-js/faker";
|
|
2
|
-
import dayjs from "dayjs";
|
|
3
|
-
|
|
4
|
-
// Supports optional parameters
|
|
5
|
-
type FakerMapping = Record<
|
|
6
|
-
string,
|
|
7
|
-
((param?: string) => string) | (() => string)
|
|
8
|
-
>;
|
|
9
|
-
|
|
10
|
-
const fakerMapping: FakerMapping = {
|
|
11
|
-
"First Name": () => faker.person.middleName(),
|
|
12
|
-
Name: () => faker.person.middleName(),
|
|
13
|
-
"Last Name": () => faker.person.middleName(),
|
|
14
|
-
Email: () => faker.internet.email(),
|
|
15
|
-
"Phone Number": () => faker.string.numeric(10),
|
|
16
|
-
Number: () => faker.string.numeric(11),
|
|
17
|
-
"Complete Number": () => faker.string.numeric(11),
|
|
18
|
-
"App Colour": () => faker.color.rgb(),
|
|
19
|
-
"App Name": () => faker.commerce.productName(),
|
|
20
|
-
"Role Name": () => faker.person.jobTitle(),
|
|
21
|
-
"Company Name": () => faker.company.name(),
|
|
22
|
-
"Full Name": () => faker.person.fullName(),
|
|
23
|
-
"Disposable Email": () =>
|
|
24
|
-
faker.internet.email({ provider: "inboxkitten.com" }),
|
|
25
|
-
"ALpha Numeric": () => faker.string.numeric(11) + "e",
|
|
26
|
-
"Lorem Word": () => faker.lorem.sentences({ min: 1, max: 3 }),
|
|
27
|
-
Word: () => faker.lorem.word({ length: { min: 5, max: 11 } }),
|
|
28
|
-
"Current Date": () => dayjs().format("YYYY-MM-DD"),
|
|
29
|
-
"Current Date2": () => new Date().toISOString().split("T")[0],
|
|
30
|
-
|
|
31
|
-
MonthsFromNow: (months?: string) => {
|
|
32
|
-
const monthsToAdd = parseInt(months || "0", 10);
|
|
33
|
-
return dayjs().add(monthsToAdd, "month").format("YYYY-MM-DD");
|
|
34
|
-
},
|
|
35
|
-
MonthsAgo: (months?: string) => {
|
|
36
|
-
const monthsToSubtract = parseInt(months || "0", 10);
|
|
37
|
-
return dayjs().subtract(monthsToSubtract, "month").format("YYYY-MM-DD");
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
WeeksFromNow: (weeks?: string) => {
|
|
41
|
-
const weeksToAdd = parseInt(weeks || "0", 10);
|
|
42
|
-
return dayjs().add(weeksToAdd, "week").format("YYYY-MM-DD");
|
|
43
|
-
},
|
|
44
|
-
WeeksAgo: (weeks?: string) => {
|
|
45
|
-
const weeksToSubtract = parseInt(weeks || "0", 10);
|
|
46
|
-
return dayjs().subtract(weeksToSubtract, "week").format("YYYY-MM-DD");
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
DaysFromNow: (days?: string) => {
|
|
50
|
-
const daysToAdd = parseInt(days || "0", 10);
|
|
51
|
-
return dayjs().add(daysToAdd, "day").format("YYYY-MM-DD");
|
|
52
|
-
},
|
|
53
|
-
DaysAgo: (days?: string) => {
|
|
54
|
-
const daysToSubtract = parseInt(days || "0", 10);
|
|
55
|
-
return dayjs().subtract(daysToSubtract, "day").format("YYYY-MM-DD");
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
export function evaluateFaker(value: string): string {
|
|
60
|
-
const [key, param] = value.split(":");
|
|
61
|
-
|
|
62
|
-
const fn = fakerMapping[key];
|
|
63
|
-
if (typeof fn === "function") {
|
|
64
|
-
return fn(param);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return value; // fallback to raw value if not mapped
|
|
68
|
-
}
|
package/helpers/utils/index.ts
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
//optionsUtils.ts
|
|
2
|
-
import type { DataTable } from "@cucumber/cucumber";
|
|
3
|
-
import type { Locator } from "@playwright/test";
|
|
4
|
-
|
|
5
|
-
type ClickOptions = Parameters<Locator["click"]>[0];
|
|
6
|
-
type DblClickOptions = Parameters<Locator["dblclick"]>[0];
|
|
7
|
-
type HoverOptions = Parameters<Locator["hover"]>[0];
|
|
8
|
-
type FillOptions = Parameters<Locator["fill"]>[1];
|
|
9
|
-
type TypeOptions = Parameters<Locator["type"]>[1];
|
|
10
|
-
type CheckOptions = Parameters<Locator["check"]>[0];
|
|
11
|
-
type UncheckOptions = Parameters<Locator["uncheck"]>[0];
|
|
12
|
-
type SelectOptionOptions = Parameters<Locator["selectOption"]>[1];
|
|
13
|
-
|
|
14
|
-
type KeyboardModifier = NonNullable<
|
|
15
|
-
NonNullable<ClickOptions>["modifiers"]
|
|
16
|
-
>[number];
|
|
17
|
-
|
|
18
|
-
export function parseClickOptions(table?: DataTable): Partial<ClickOptions> {
|
|
19
|
-
return parseGenericOptions(table) as Partial<ClickOptions>;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function parseDblClickOptions(
|
|
23
|
-
table?: DataTable
|
|
24
|
-
): Partial<DblClickOptions> {
|
|
25
|
-
return parseGenericOptions(table) as Partial<DblClickOptions>;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function parseHoverOptions(table?: DataTable): Partial<HoverOptions> {
|
|
29
|
-
return parseGenericOptions(table) as Partial<HoverOptions>;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function parseTypeOptions(table?: DataTable): Partial<TypeOptions> {
|
|
33
|
-
return parseGenericOptions(table) as unknown as Partial<TypeOptions>;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function parseFillOptions(table?: DataTable): Partial<FillOptions> {
|
|
37
|
-
return parseGenericOptions(table) as Partial<FillOptions>;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function parseCheckOptions(table?: DataTable): Partial<CheckOptions> {
|
|
41
|
-
return parseGenericOptions(table) as Partial<CheckOptions>;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function parseUncheckOptions(
|
|
45
|
-
table?: DataTable
|
|
46
|
-
): Partial<UncheckOptions> {
|
|
47
|
-
return parseGenericOptions(table) as Partial<UncheckOptions>;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function parseSelectOptions(
|
|
51
|
-
table?: DataTable
|
|
52
|
-
): Partial<SelectOptionOptions> {
|
|
53
|
-
return parseGenericOptions(table) as Partial<SelectOptionOptions>;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function parseGenericOptions(table?: DataTable): Record<string, any> {
|
|
57
|
-
if (!table) return {};
|
|
58
|
-
|
|
59
|
-
const options: Record<string, any> = {};
|
|
60
|
-
const rows = table.raw();
|
|
61
|
-
|
|
62
|
-
for (const [key, value] of rows) {
|
|
63
|
-
switch (key) {
|
|
64
|
-
case "timeout":
|
|
65
|
-
case "delay":
|
|
66
|
-
case "clickCount":
|
|
67
|
-
options[key] = Number(value);
|
|
68
|
-
break;
|
|
69
|
-
|
|
70
|
-
case "force":
|
|
71
|
-
case "noWaitAfter":
|
|
72
|
-
case "strict":
|
|
73
|
-
case "trial":
|
|
74
|
-
options[key] = value === "true";
|
|
75
|
-
break;
|
|
76
|
-
|
|
77
|
-
case "modifiers":
|
|
78
|
-
options.modifiers = value
|
|
79
|
-
.split(",")
|
|
80
|
-
.map((v) => v.trim() as KeyboardModifier);
|
|
81
|
-
break;
|
|
82
|
-
|
|
83
|
-
case "button":
|
|
84
|
-
if (["left", "middle", "right"].includes(value)) {
|
|
85
|
-
options.button = value;
|
|
86
|
-
} else {
|
|
87
|
-
throw new Error(`Invalid button option: "${value}"`);
|
|
88
|
-
}
|
|
89
|
-
break;
|
|
90
|
-
|
|
91
|
-
case "position":
|
|
92
|
-
const [x, y] = value.split(",").map((n) => Number(n.trim()));
|
|
93
|
-
if (isNaN(x) || isNaN(y)) {
|
|
94
|
-
throw new Error(`Invalid position format: "${value}"`);
|
|
95
|
-
}
|
|
96
|
-
options.position = { x, y };
|
|
97
|
-
break;
|
|
98
|
-
|
|
99
|
-
default:
|
|
100
|
-
console.warn(`[⚠️ parseGenericOptions] Unknown option "${key}"`);
|
|
101
|
-
break;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return options;
|
|
106
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import type { CustomWorld } from "../world";
|
|
4
|
-
|
|
5
|
-
// Dynamic resolver
|
|
6
|
-
export function resolveValue(input: string): string {
|
|
7
|
-
// Uppercase = environment variable (e.g. TEST_USER)
|
|
8
|
-
if (/^[A-Z0-9_]+$/.test(input)) {
|
|
9
|
-
const envVal = process.env[input];
|
|
10
|
-
if (!envVal) {
|
|
11
|
-
throw new Error(`Environment variable ${input} not found.`);
|
|
12
|
-
}
|
|
13
|
-
return envVal;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// Dot = JSON reference (e.g. userData.email)
|
|
17
|
-
if (input.includes(".")) {
|
|
18
|
-
const [fileName, fieldName] = input.split(".");
|
|
19
|
-
const jsonPath = path.resolve(
|
|
20
|
-
"e2e/support/helper/test-data",
|
|
21
|
-
`${fileName}.json`
|
|
22
|
-
);
|
|
23
|
-
if (fs.existsSync(jsonPath)) {
|
|
24
|
-
const json = JSON.parse(fs.readFileSync(jsonPath, "utf-8"));
|
|
25
|
-
const value = json[fieldName];
|
|
26
|
-
if (value !== undefined) return value;
|
|
27
|
-
throw new Error(`Field "${fieldName}" not found in ${fileName}.json`);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Default to hardcoded value
|
|
32
|
-
return input;
|
|
33
|
-
}
|
|
34
|
-
export function resolveLoginValue(
|
|
35
|
-
raw: string,
|
|
36
|
-
world: CustomWorld
|
|
37
|
-
): string | undefined {
|
|
38
|
-
// ✅ Alias: @aliasName
|
|
39
|
-
if (raw.startsWith("@")) {
|
|
40
|
-
return world.data[raw.slice(1)];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// ✅ JSON: user.json:key
|
|
44
|
-
if (raw.includes(".json:")) {
|
|
45
|
-
const [filename, key] = raw.split(".json:");
|
|
46
|
-
const filePath = path.resolve("test-data", `${filename}.json`);
|
|
47
|
-
if (!fs.existsSync(filePath)) {
|
|
48
|
-
throw new Error(`JSON fixture not found: ${filename}.json`);
|
|
49
|
-
}
|
|
50
|
-
const fileData = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
51
|
-
return fileData[key];
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// ✅ Fallback to raw value
|
|
55
|
-
return raw;
|
|
56
|
-
}
|
|
57
|
-
// Determine session name from email or username
|
|
58
|
-
export function deriveSessionName(
|
|
59
|
-
emailOrUser: string,
|
|
60
|
-
fallback = "default"
|
|
61
|
-
): string {
|
|
62
|
-
if (!emailOrUser) return `${fallback}User`;
|
|
63
|
-
|
|
64
|
-
const base = emailOrUser.includes("@")
|
|
65
|
-
? emailOrUser.split("@")[0]
|
|
66
|
-
: emailOrUser;
|
|
67
|
-
|
|
68
|
-
return `${base}User`;
|
|
69
|
-
}
|
package/helpers/world.ts
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
// support/world.ts
|
|
2
|
-
import {
|
|
3
|
-
setWorldConstructor,
|
|
4
|
-
World,
|
|
5
|
-
ITestCaseHookParameter,
|
|
6
|
-
} from "@cucumber/cucumber";
|
|
7
|
-
import {
|
|
8
|
-
Browser,
|
|
9
|
-
Page,
|
|
10
|
-
BrowserContext,
|
|
11
|
-
Locator,
|
|
12
|
-
FrameLocator,
|
|
13
|
-
devices,
|
|
14
|
-
} from "@playwright/test";
|
|
15
|
-
import { chromium } from "playwright";
|
|
16
|
-
import * as dotenv from "dotenv";
|
|
17
|
-
|
|
18
|
-
dotenv.config();
|
|
19
|
-
|
|
20
|
-
const isHeadless = process.env.HEADLESS !== "false";
|
|
21
|
-
const slowMo = process.env.SLOWMO ? Number(process.env.SLOWMO) : 0;
|
|
22
|
-
|
|
23
|
-
export class CustomWorld extends World {
|
|
24
|
-
browser!: Browser;
|
|
25
|
-
context!: BrowserContext;
|
|
26
|
-
page!: Page;
|
|
27
|
-
|
|
28
|
-
elements?: Locator;
|
|
29
|
-
element?: Locator;
|
|
30
|
-
frame?: FrameLocator;
|
|
31
|
-
|
|
32
|
-
data: Record<string, any> = {};
|
|
33
|
-
logs: string[] = [];
|
|
34
|
-
testName?: string;
|
|
35
|
-
|
|
36
|
-
async init(testInfo?: ITestCaseHookParameter) {
|
|
37
|
-
const isMobile = testInfo?.pickle.tags.some(
|
|
38
|
-
(tag) => tag.name === "@mobile"
|
|
39
|
-
);
|
|
40
|
-
const device = isMobile ? devices["Pixel 5"] : undefined;
|
|
41
|
-
|
|
42
|
-
this.browser = await chromium.launch({ headless: isHeadless, slowMo });
|
|
43
|
-
|
|
44
|
-
this.context = await this.browser.newContext({
|
|
45
|
-
...(device || {}),
|
|
46
|
-
recordVideo: { dir: "e2e/test-artifacts/videos" },
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
this.page = await this.context.newPage();
|
|
50
|
-
this.testName = testInfo?.pickle.name;
|
|
51
|
-
this.log(`🧪 Initialized context${isMobile ? " (mobile)" : ""}`);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
getScope(): Page | FrameLocator {
|
|
55
|
-
return this.frame ?? this.page;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
exitIframe() {
|
|
59
|
-
this.frame = undefined;
|
|
60
|
-
this.log("⬅️ Exited iframe, scope is now main page");
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
log = (message: string) => {
|
|
64
|
-
this.logs.push(message);
|
|
65
|
-
console.log(`[LOG] ${message}`);
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
async cleanup(testInfo?: ITestCaseHookParameter) {
|
|
69
|
-
const failed = testInfo?.result?.status === "FAILED";
|
|
70
|
-
|
|
71
|
-
try {
|
|
72
|
-
await this.page?.close();
|
|
73
|
-
} catch (err) {
|
|
74
|
-
this.log(`⚠️ Error closing page: ${(err as Error).message}`);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
await this.context?.close();
|
|
79
|
-
} catch (err) {
|
|
80
|
-
this.log(`⚠️ Error closing context: ${(err as Error).message}`);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
try {
|
|
84
|
-
await this.browser?.close();
|
|
85
|
-
} catch (err) {
|
|
86
|
-
this.log(`⚠️ Error closing browser: ${(err as Error).message}`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
setWorldConstructor(CustomWorld);
|