playwright-cucumber-ts-steps 1.0.1 → 1.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/README.md +195 -256
- package/dist/backend/actions/index.js +4 -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 +4 -0
- package/dist/backend/api/requests.js +24 -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/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 +136 -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
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.apiState = void 0;
|
|
4
|
+
// A simple storage to hold the response between the "When" and "Then" steps
|
|
5
|
+
let lastResponse = null;
|
|
6
|
+
exports.apiState = {
|
|
7
|
+
setResponse: (response) => {
|
|
8
|
+
lastResponse = response;
|
|
9
|
+
},
|
|
10
|
+
getResponse: () => {
|
|
11
|
+
if (!lastResponse)
|
|
12
|
+
throw new Error("No API response found. Did you run a 'When I make a request' step first?");
|
|
13
|
+
return lastResponse;
|
|
14
|
+
},
|
|
15
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.expectVisible = expectVisible;
|
|
4
|
+
async function expectVisible(page, selector, timeout = 2000) {
|
|
5
|
+
const el = await page.waitForSelector(selector, { state: 'visible', timeout });
|
|
6
|
+
if (!el)
|
|
7
|
+
throw new Error(`Element ${selector} not visible`);
|
|
8
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const test_1 = require("@playwright/test");
|
|
4
|
+
const registry_1 = require("../../core/registry");
|
|
5
|
+
// URL Check
|
|
6
|
+
(0, registry_1.Step)("I expect the url to be {string}", async (page, url) => {
|
|
7
|
+
await (0, test_1.expect)(page).toHaveURL(url);
|
|
8
|
+
});
|
|
9
|
+
// Partial URL Check
|
|
10
|
+
(0, registry_1.Step)("I expect the url to contain {string}", async (page, part) => {
|
|
11
|
+
// We use a RegExp to allow partial matching safely
|
|
12
|
+
await (0, test_1.expect)(page).toHaveURL(new RegExp(part));
|
|
13
|
+
});
|
|
14
|
+
// Title Check
|
|
15
|
+
(0, registry_1.Step)("I expect the title to be {string}", async (page, title) => {
|
|
16
|
+
await (0, test_1.expect)(page).toHaveTitle(title);
|
|
17
|
+
});
|
|
18
|
+
// Partial Title Check
|
|
19
|
+
(0, registry_1.Step)("I expect the title to contain {string}", async (page, titlePart) => {
|
|
20
|
+
await (0, test_1.expect)(page).toHaveTitle(new RegExp(titlePart));
|
|
21
|
+
});
|
|
22
|
+
// Screenshot Match (Visual Regression)
|
|
23
|
+
(0, registry_1.Step)("I expect the page screenshot to match {string}", async (page, filename) => {
|
|
24
|
+
await (0, test_1.expect)(page).toHaveScreenshot(filename);
|
|
25
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const test_1 = require("@playwright/test");
|
|
4
|
+
const registry_1 = require("../../core/registry");
|
|
5
|
+
// Exact text match
|
|
6
|
+
(0, registry_1.Step)("I expect {string} to have text {string}", async (page, selector, text) => {
|
|
7
|
+
await (0, test_1.expect)(page.locator(selector)).toHaveText(text);
|
|
8
|
+
});
|
|
9
|
+
// Partial text match (contains)
|
|
10
|
+
(0, registry_1.Step)("I expect {string} to contain text {string}", async (page, selector, text) => {
|
|
11
|
+
await (0, test_1.expect)(page.locator(selector)).toContainText(text);
|
|
12
|
+
});
|
|
13
|
+
// Check input value (for form fields)
|
|
14
|
+
(0, registry_1.Step)("I expect {string} to have value {string}", async (page, selector, value) => {
|
|
15
|
+
await (0, test_1.expect)(page.locator(selector)).toHaveValue(value);
|
|
16
|
+
});
|
|
17
|
+
// Check an attribute (e.g., class, href, src)
|
|
18
|
+
(0, registry_1.Step)("I expect {string} to have attribute {string} with value {string}", async (page, selector, attr, value) => {
|
|
19
|
+
await (0, test_1.expect)(page.locator(selector)).toHaveAttribute(attr, value);
|
|
20
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const test_1 = require("@playwright/test");
|
|
4
|
+
const registry_1 = require("../../core/registry");
|
|
5
|
+
// Check if element exists and is visible
|
|
6
|
+
(0, registry_1.Step)("I expect {string} to be visible", async (page, selector) => {
|
|
7
|
+
await (0, test_1.expect)(page.locator(selector)).toBeVisible();
|
|
8
|
+
});
|
|
9
|
+
// Check if element is hidden (or doesn't exist)
|
|
10
|
+
(0, registry_1.Step)("I expect {string} to be hidden", async (page, selector) => {
|
|
11
|
+
await (0, test_1.expect)(page.locator(selector)).toBeHidden();
|
|
12
|
+
});
|
|
13
|
+
// Check if element is enabled (clickable)
|
|
14
|
+
(0, registry_1.Step)("I expect {string} to be enabled", async (page, selector) => {
|
|
15
|
+
await (0, test_1.expect)(page.locator(selector)).toBeEnabled();
|
|
16
|
+
});
|
|
17
|
+
// Check if element is disabled
|
|
18
|
+
(0, registry_1.Step)("I expect {string} to be disabled", async (page, selector) => {
|
|
19
|
+
await (0, test_1.expect)(page.locator(selector)).toBeDisabled();
|
|
20
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const registry_1 = require("../../core/registry");
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
// 1. SAVE STATE (Use this after a successful login)
|
|
40
|
+
// Example: And I save the browser state to "admin.json"
|
|
41
|
+
(0, registry_1.Step)("I save the browser state to {string}", async (page, filename) => {
|
|
42
|
+
// Ensure the directory exists
|
|
43
|
+
const authDir = path.resolve(process.cwd(), "auth");
|
|
44
|
+
if (!fs.existsSync(authDir)) {
|
|
45
|
+
fs.mkdirSync(authDir);
|
|
46
|
+
}
|
|
47
|
+
const filePath = path.resolve(authDir, filename);
|
|
48
|
+
await page.context().storageState({ path: filePath });
|
|
49
|
+
console.log(`✅ State saved to: ${filePath}`);
|
|
50
|
+
});
|
|
51
|
+
// 2. LOAD STATE (Use this at the start of other scenarios)
|
|
52
|
+
// Example: Given I load the browser state from "admin.json"
|
|
53
|
+
(0, registry_1.Step)("I load the browser state from {string}", async (page, filename) => {
|
|
54
|
+
const filePath = path.resolve(process.cwd(), "auth", filename);
|
|
55
|
+
if (!fs.existsSync(filePath)) {
|
|
56
|
+
throw new Error(`❌ Auth file not found at: ${filePath}. Did you run the login scenario first?`);
|
|
57
|
+
}
|
|
58
|
+
// Playwright normally loads state at context creation.
|
|
59
|
+
// To load it dynamically mid-test, we need to add cookies/origins to the current context.
|
|
60
|
+
const state = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
61
|
+
if (state.cookies) {
|
|
62
|
+
await page.context().addCookies(state.cookies);
|
|
63
|
+
}
|
|
64
|
+
if (state.origins) {
|
|
65
|
+
// LocalStorage requires a bit more work, usually handled by context creation,
|
|
66
|
+
// but adding cookies is often enough for 90% of apps.
|
|
67
|
+
// For full local storage support, users should configure this in playwright.config.ts
|
|
68
|
+
// but this step is a great dynamic helper.
|
|
69
|
+
}
|
|
70
|
+
console.log(`✅ Loaded session for: ${filename}`);
|
|
71
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const registry_1 = require("../../core/registry");
|
|
4
|
+
// Accept the next popup (Alert/Confirm/Prompt)
|
|
5
|
+
(0, registry_1.Step)("I accept the next dialog", async (page) => {
|
|
6
|
+
page.once("dialog", async (dialog) => {
|
|
7
|
+
await dialog.accept();
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
// Dismiss (Cancel) the next popup
|
|
11
|
+
(0, registry_1.Step)("I dismiss the next dialog", async (page) => {
|
|
12
|
+
page.once("dialog", async (dialog) => {
|
|
13
|
+
await dialog.dismiss();
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
// Type text into a prompt and accept
|
|
17
|
+
(0, registry_1.Step)("I type {string} into the next prompt and accept", async (page, text) => {
|
|
18
|
+
page.once("dialog", async (dialog) => {
|
|
19
|
+
await dialog.accept(text);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const registry_1 = require("../../core/registry");
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
// Dropdown: Select by value or label
|
|
39
|
+
(0, registry_1.Step)("I select option {string} from {string}", async (page, option, selector) => {
|
|
40
|
+
await page.selectOption(selector, { label: option }).catch(() => {
|
|
41
|
+
// Fallback: try selecting by value if label fails
|
|
42
|
+
return page.selectOption(selector, { value: option });
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
// Checkboxes & Radio Buttons
|
|
46
|
+
(0, registry_1.Step)("I check {string}", async (page, selector) => {
|
|
47
|
+
await page.check(selector);
|
|
48
|
+
});
|
|
49
|
+
(0, registry_1.Step)("I uncheck {string}", async (page, selector) => {
|
|
50
|
+
await page.uncheck(selector);
|
|
51
|
+
});
|
|
52
|
+
// File Upload
|
|
53
|
+
// Usage: When I upload file "data.csv" to "#upload-input"
|
|
54
|
+
(0, registry_1.Step)("I upload file {string} to {string}", async (page, fileName, selector) => {
|
|
55
|
+
// We assume the user puts files in a 'fixtures' or root folder.
|
|
56
|
+
// You might want to customize this path resolution.
|
|
57
|
+
const filePath = path.resolve(process.cwd(), fileName);
|
|
58
|
+
await page.setInputFiles(selector, filePath);
|
|
59
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const registry_1 = require("../../core/registry");
|
|
4
|
+
// Click inside a specific frame
|
|
5
|
+
(0, registry_1.Step)("I click {string} inside frame {string}", async (page, elementSelector, frameSelector) => {
|
|
6
|
+
const frame = page.frameLocator(frameSelector);
|
|
7
|
+
await frame.locator(elementSelector).click();
|
|
8
|
+
});
|
|
9
|
+
// Fill input inside a specific frame
|
|
10
|
+
(0, registry_1.Step)("I fill {string} inside frame {string} with {string}", async (page, elementSelector, frameSelector, value) => {
|
|
11
|
+
const frame = page.frameLocator(frameSelector);
|
|
12
|
+
await frame.locator(elementSelector).fill(value);
|
|
13
|
+
});
|
|
14
|
+
// Assert text inside a specific frame
|
|
15
|
+
(0, registry_1.Step)("I expect {string} inside frame {string} to have text {string}", async (page, elementSelector, frameSelector, text) => {
|
|
16
|
+
const frame = page.frameLocator(frameSelector);
|
|
17
|
+
const locator = frame.locator(elementSelector);
|
|
18
|
+
// We need to import expect for this assertion, or use a custom check
|
|
19
|
+
// Since we are inside 'elements', let's just do a basic wait/check or re-export expect
|
|
20
|
+
await locator.waitFor();
|
|
21
|
+
const actualText = await locator.textContent();
|
|
22
|
+
if (!actualText?.includes(text)) {
|
|
23
|
+
throw new Error(`Expected text "${text}" but found "${actualText}" in frame`);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.stepRegistry = void 0;
|
|
4
|
+
exports.Step = Step;
|
|
5
|
+
// src/core/registry.ts
|
|
6
|
+
const cucumber_expressions_1 = require("@cucumber/cucumber-expressions");
|
|
7
|
+
// 2. The Global Registry
|
|
8
|
+
// This array stores every step you create in the backend folder
|
|
9
|
+
exports.stepRegistry = [];
|
|
10
|
+
const parameterTypeRegistry = new cucumber_expressions_1.ParameterTypeRegistry();
|
|
11
|
+
// 3. The Function to Register Steps
|
|
12
|
+
// You will use this inside src/backend/actions/...
|
|
13
|
+
function Step(pattern, fn) {
|
|
14
|
+
const expression = new cucumber_expressions_1.CucumberExpression(pattern, parameterTypeRegistry);
|
|
15
|
+
exports.stepRegistry.push({
|
|
16
|
+
expression,
|
|
17
|
+
fn,
|
|
18
|
+
pattern,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.runTests = runTests;
|
|
37
|
+
// src/core/runner.ts
|
|
38
|
+
const test_1 = require("@playwright/test");
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const glob_1 = require("glob");
|
|
41
|
+
const registry_1 = require("./registry");
|
|
42
|
+
// LOAD BACKEND LIBRARIES
|
|
43
|
+
require("../backend/actions/index");
|
|
44
|
+
require("../backend/assertions/index");
|
|
45
|
+
require("../backend/elements/index");
|
|
46
|
+
require("../backend/api/index");
|
|
47
|
+
require("../backend/auth/index");
|
|
48
|
+
/**
|
|
49
|
+
* The main test runner. Parses feature files and executes them as Playwright tests.
|
|
50
|
+
* * @param featureGlob - Glob pattern to find feature files (e.g., 'features/*.feature')
|
|
51
|
+
* @param options - Configuration options for filtering tags
|
|
52
|
+
* * @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* // Run all features
|
|
55
|
+
* runTests('features/*.feature');
|
|
56
|
+
* * // Run only @smoke tests
|
|
57
|
+
* runTests('features/*.feature', { tags: '@smoke' });
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
function runTests(featureGlob, options) {
|
|
61
|
+
const files = (0, glob_1.globSync)(featureGlob);
|
|
62
|
+
for (const file of files) {
|
|
63
|
+
const content = fs.readFileSync(file, "utf8");
|
|
64
|
+
const featureMatch = content.match(/Feature:\s*(.+)/);
|
|
65
|
+
const featureName = featureMatch
|
|
66
|
+
? featureMatch[1].trim()
|
|
67
|
+
: "Unnamed Feature";
|
|
68
|
+
test_1.test.describe(featureName, () => {
|
|
69
|
+
const scenarioRegex = /(?:(@[\w\s@]+)\s+)?Scenario:\s*(.+)/g;
|
|
70
|
+
let match;
|
|
71
|
+
while ((match = scenarioRegex.exec(content)) !== null) {
|
|
72
|
+
// 1. CAPTURE DATA IMMEDIATELY
|
|
73
|
+
// We calculate everything here so we don't rely on 'match' later
|
|
74
|
+
const foundTags = match[1] || "";
|
|
75
|
+
const scenarioName = match[2].trim();
|
|
76
|
+
const startIndex = match.index + match[0].length;
|
|
77
|
+
const nextMatchIndex = content.slice(startIndex).search(/Scenario:/);
|
|
78
|
+
const blockEnd = nextMatchIndex === -1 ? content.length : startIndex + nextMatchIndex;
|
|
79
|
+
// This variable 'scenarioBlock' is now safe to use inside the test
|
|
80
|
+
const scenarioBlock = content.slice(startIndex, blockEnd);
|
|
81
|
+
if (options?.tags && !foundTags.includes(options.tags)) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
(0, test_1.test)(scenarioName, async ({ page }, testInfo) => {
|
|
85
|
+
// 1. Calculate the scenario block (Same as before)
|
|
86
|
+
const lines = scenarioBlock
|
|
87
|
+
.trim()
|
|
88
|
+
.split("\n")
|
|
89
|
+
.map((l) => l.trim())
|
|
90
|
+
.filter((l) => l);
|
|
91
|
+
for (const stepText of lines) {
|
|
92
|
+
if (stepText.startsWith("#") ||
|
|
93
|
+
stepText.startsWith("@") ||
|
|
94
|
+
stepText === "")
|
|
95
|
+
continue;
|
|
96
|
+
const cleanStep = stepText.replace(/^(Given|When|Then|And|But)\s+/i, "");
|
|
97
|
+
const stepDef = findMatchingStep(cleanStep);
|
|
98
|
+
if (!stepDef) {
|
|
99
|
+
throw new Error(`❌ Undefined Step: "${cleanStep}"`);
|
|
100
|
+
}
|
|
101
|
+
// 2. ERROR HANDLING WRAPPER
|
|
102
|
+
try {
|
|
103
|
+
// Execute the step
|
|
104
|
+
await stepDef.fn(page, ...stepDef.args);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
// 3. CAPTURE EVIDENCE ON FAILURE
|
|
108
|
+
console.error(`❌ Failed at step: "${stepText}"`);
|
|
109
|
+
// Take a screenshot immediately
|
|
110
|
+
const screenshot = await page.screenshot({
|
|
111
|
+
fullPage: true,
|
|
112
|
+
type: "png",
|
|
113
|
+
});
|
|
114
|
+
// Attach it to the report (HTML/JSON/Slack will pick this up)
|
|
115
|
+
await testInfo.attach("failure-screenshot", {
|
|
116
|
+
body: screenshot,
|
|
117
|
+
contentType: "image/png",
|
|
118
|
+
});
|
|
119
|
+
// Re-throw the error so the test is marked as Failed
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function findMatchingStep(text) {
|
|
129
|
+
for (const step of registry_1.stepRegistry) {
|
|
130
|
+
const match = step.expression.match(text);
|
|
131
|
+
if (match) {
|
|
132
|
+
return { fn: step.fn, args: match.map((arg) => arg.getValue(null)) };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getReporters = exports.Step = exports.runTests = void 0;
|
|
4
|
+
// src/index.ts
|
|
5
|
+
var runner_1 = require("./core/runner");
|
|
6
|
+
Object.defineProperty(exports, "runTests", { enumerable: true, get: function () { return runner_1.runTests; } });
|
|
7
|
+
var registry_1 = require("./core/registry"); // Export this in case users want to add custom steps
|
|
8
|
+
Object.defineProperty(exports, "Step", { enumerable: true, get: function () { return registry_1.Step; } });
|
|
9
|
+
var index_1 = require("./reporting/index");
|
|
10
|
+
Object.defineProperty(exports, "getReporters", { enumerable: true, get: function () { return index_1.getReporters; } });
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/reporting/index.ts
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.getReporters = getReporters;
|
|
5
|
+
/**
|
|
6
|
+
* Helper to generate Playwright Reporter configuration.
|
|
7
|
+
* * @param options - Select which reporters to enable ('html', 'slack', etc.)
|
|
8
|
+
* @returns An array suitable for the 'reporter' field in playwright.config.ts
|
|
9
|
+
*/
|
|
10
|
+
function getReporters(options) {
|
|
11
|
+
const reporters = [];
|
|
12
|
+
// 1. ALWAYS ADD LIST (So they see progress in terminal)
|
|
13
|
+
reporters.push(["list"]);
|
|
14
|
+
// 2. HTML REPORTER (Priority 1)
|
|
15
|
+
if (options.on.includes("html")) {
|
|
16
|
+
reporters.push([
|
|
17
|
+
"html",
|
|
18
|
+
{ open: "on-failure", outputFolder: "playwright-report" },
|
|
19
|
+
]);
|
|
20
|
+
}
|
|
21
|
+
// 3. JSON REPORTER (Useful for CI)
|
|
22
|
+
if (options.on.includes("json")) {
|
|
23
|
+
reporters.push(["json", { outputFile: "results.json" }]);
|
|
24
|
+
}
|
|
25
|
+
// 4. SLACK REPORTER (Requires external package)
|
|
26
|
+
if (options.on.includes("slack")) {
|
|
27
|
+
if (!options.slackWebhookUrl) {
|
|
28
|
+
console.warn("⚠️ Slack reporter requested but no Webhook URL provided.");
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
// We assume the user has installed 'playwright-slack-report'
|
|
32
|
+
// We return the config string that Playwright expects
|
|
33
|
+
reporters.push([
|
|
34
|
+
"playwright-slack-report",
|
|
35
|
+
{
|
|
36
|
+
sendResults: "always", // 'always' | 'on-failure' | 'off'
|
|
37
|
+
slackWebHookUrl: options.slackWebhookUrl,
|
|
38
|
+
},
|
|
39
|
+
]);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return reporters;
|
|
43
|
+
}
|
package/package.json
CHANGED
|
@@ -1,113 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "playwright-cucumber-ts-steps",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
},
|
|
11
|
-
"exports": {
|
|
12
|
-
".": {
|
|
13
|
-
"import": "./lib/index.js"
|
|
14
|
-
},
|
|
15
|
-
"./register": {
|
|
16
|
-
"import": "./lib/register.js"
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
"sideEffects": false,
|
|
20
|
-
"typesVersions": {
|
|
21
|
-
"*": {
|
|
22
|
-
"*": [
|
|
23
|
-
"lib/*"
|
|
24
|
-
]
|
|
25
|
-
}
|
|
26
|
-
},
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"private": false,
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
27
10
|
"scripts": {
|
|
28
11
|
"build": "tsc",
|
|
29
12
|
"docs": "typedoc",
|
|
30
|
-
"
|
|
31
|
-
"prepare": "husky",
|
|
32
|
-
"lint": "eslint . --ext .ts",
|
|
33
|
-
"lint:fix": "eslint . --ext .ts --fix",
|
|
34
|
-
"lint:tsc": "tsc --noEmit",
|
|
35
|
-
"format": "prettier --write .",
|
|
36
|
-
"clean": "rm -rf playwright/report",
|
|
37
|
-
"test": "npm run clean && cucumber-js --config playwright/config/cucumber.js",
|
|
38
|
-
"commitlint": "commitlint --edit"
|
|
39
|
-
},
|
|
40
|
-
"lint-staged": {
|
|
41
|
-
"*.{js,ts,tsx,json,md}": [
|
|
42
|
-
"eslint --fix",
|
|
43
|
-
"prettier --write"
|
|
44
|
-
]
|
|
45
|
-
},
|
|
46
|
-
"commitlint": {
|
|
47
|
-
"extends": [
|
|
48
|
-
"@commitlint/config-conventional"
|
|
49
|
-
]
|
|
13
|
+
"prepublishOnly": "npm run build"
|
|
50
14
|
},
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
"playwright",
|
|
57
|
-
"cucumber",
|
|
58
|
-
"typescript",
|
|
59
|
-
"e2e",
|
|
60
|
-
"steps",
|
|
61
|
-
"step-definitions",
|
|
62
|
-
"testing",
|
|
63
|
-
"automation",
|
|
64
|
-
"bdd",
|
|
65
|
-
"api",
|
|
66
|
-
"ui",
|
|
67
|
-
"web",
|
|
68
|
-
"end-to-end",
|
|
69
|
-
"behavior-driven",
|
|
70
|
-
"mobile",
|
|
71
|
-
"visual-testing"
|
|
72
|
-
],
|
|
73
|
-
"author": "qaPaschalE <paschal.enyimiri@gmail.com>",
|
|
74
|
-
"license": "MIT",
|
|
75
|
-
"bugs": {
|
|
76
|
-
"url": "https://github.com/qaPaschalE/playwright-cucumber-ts-steps/issues"
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@cucumber/cucumber-expressions": "^17.1.0",
|
|
17
|
+
"@cucumber/gherkin": "^27.0.0",
|
|
18
|
+
"@cucumber/messages": "^22.0.0",
|
|
19
|
+
"glob": "^10.3.10"
|
|
77
20
|
},
|
|
78
|
-
"homepage": "https://github.com/qaPaschalE/playwright-cucumber-ts-steps#readme",
|
|
79
21
|
"peerDependencies": {
|
|
80
|
-
"@
|
|
81
|
-
"@playwright/test": "*"
|
|
82
|
-
},
|
|
83
|
-
"files": [
|
|
84
|
-
"lib/",
|
|
85
|
-
"src/"
|
|
86
|
-
],
|
|
87
|
-
"dependencies": {
|
|
88
|
-
"@cucumber/tag-expressions": "^6.2.0",
|
|
89
|
-
"@faker-js/faker": "^9.8.0",
|
|
90
|
-
"dayjs": "^1.11.13",
|
|
91
|
-
"dotenv-cli": "^8.0.0",
|
|
92
|
-
"glob": "^11.0.3",
|
|
93
|
-
"parse": "^6.1.1",
|
|
94
|
-
"pixelmatch": "^7.1.0",
|
|
95
|
-
"pngjs": "^7.0.0"
|
|
22
|
+
"@playwright/test": "^1.0.0"
|
|
96
23
|
},
|
|
97
24
|
"devDependencies": {
|
|
98
|
-
"@
|
|
99
|
-
"@
|
|
100
|
-
"@types/
|
|
101
|
-
"
|
|
102
|
-
"
|
|
103
|
-
"eslint-plugin-import": "^2.32.0",
|
|
104
|
-
"gh-pages": "^6.3.0",
|
|
105
|
-
"husky": "^9.1.7",
|
|
106
|
-
"lint-staged": "^16.1.2",
|
|
107
|
-
"prettier": "^3.5.3",
|
|
108
|
-
"ts-node": "^10.9.2",
|
|
109
|
-
"typedoc": "^0.28.5",
|
|
110
|
-
"typescript": "^5.8.3",
|
|
111
|
-
"typescript-eslint": "^8.34.1"
|
|
25
|
+
"@playwright/test": "^1.41.0",
|
|
26
|
+
"@types/glob": "^8.1.0",
|
|
27
|
+
"@types/node": "^20.11.0",
|
|
28
|
+
"typedoc": "^0.28.15",
|
|
29
|
+
"typescript": "^5.3.3"
|
|
112
30
|
}
|
|
113
31
|
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Chetachi (Paschal) Enyimiri
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|