playwright-cucumber-ts-steps 0.1.3 → 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.
Files changed (74) hide show
  1. package/README.md +174 -39
  2. package/{dist → lib}/actions/clickSteps.js +10 -3
  3. package/{dist → lib}/actions/debugSteps.js +2 -5
  4. package/{dist → lib}/actions/elementFindSteps.js +15 -18
  5. package/lib/actions/fillFormSteps.js +130 -0
  6. package/{dist → lib}/actions/inputSteps.js +9 -76
  7. package/{dist → lib}/actions/interceptionSteps.js +8 -0
  8. package/{dist → lib}/actions/miscSteps.js +60 -12
  9. package/{dist → lib}/actions/storageSteps.js +26 -4
  10. package/{dist → lib}/assertions/buttonAndTextVisibilitySteps.js +3 -23
  11. package/{dist → lib}/assertions/elementSteps.js +7 -1
  12. package/{dist → lib}/assertions/locationSteps.js +16 -0
  13. package/{dist → lib}/assertions/semanticSteps.js +16 -3
  14. package/{dist → lib}/assertions/visualSteps.js +9 -5
  15. package/lib/custom_setups/loginHooks.js +113 -0
  16. package/lib/helpers/hooks.js +210 -0
  17. package/{dist → lib}/helpers/utils/index.d.ts +1 -0
  18. package/{dist → lib}/helpers/utils/index.js +1 -0
  19. package/{dist → lib}/helpers/utils/optionsUtils.d.ts +5 -0
  20. package/{dist → lib}/helpers/utils/optionsUtils.js +12 -3
  21. package/{dist → lib}/helpers/utils/resolveUtils.d.ts +2 -0
  22. package/{dist → lib}/helpers/utils/resolveUtils.js +13 -3
  23. package/lib/helpers/utils/sessionUtils.d.ts +3 -0
  24. package/lib/helpers/utils/sessionUtils.js +40 -0
  25. package/{dist → lib}/helpers/world.d.ts +13 -0
  26. package/{dist → lib}/helpers/world.js +17 -10
  27. package/lib/iframes/frames.d.ts +1 -0
  28. package/{dist → lib}/index.d.ts +1 -1
  29. package/{dist → lib}/index.js +1 -1
  30. package/{dist → lib}/register.js +1 -5
  31. package/package.json +40 -6
  32. package/dist/custom_setups/globalLogin.d.ts +0 -2
  33. package/dist/custom_setups/globalLogin.js +0 -25
  34. package/dist/custom_setups/loginHooks.js +0 -141
  35. package/dist/helpers/hooks.js +0 -184
  36. package/{dist → lib}/actions/clickSteps.d.ts +0 -0
  37. package/{dist → lib}/actions/cookieSteps.d.ts +0 -0
  38. package/{dist → lib}/actions/cookieSteps.js +0 -0
  39. package/{dist → lib}/actions/debugSteps.d.ts +0 -0
  40. package/{dist → lib}/actions/elementFindSteps.d.ts +0 -0
  41. package/{dist/actions/inputSteps.d.ts → lib/actions/fillFormSteps.d.ts} +0 -0
  42. package/{dist/actions/interceptionSteps.d.ts → lib/actions/inputSteps.d.ts} +0 -0
  43. package/{dist/actions/miscSteps.d.ts → lib/actions/interceptionSteps.d.ts} +0 -0
  44. package/{dist/actions/mouseSteps.d.ts → lib/actions/miscSteps.d.ts} +0 -0
  45. package/{dist/actions/scrollSteps.d.ts → lib/actions/mouseSteps.d.ts} +0 -0
  46. package/{dist → lib}/actions/mouseSteps.js +0 -0
  47. package/{dist/actions/storageSteps.d.ts → lib/actions/scrollSteps.d.ts} +0 -0
  48. package/{dist → lib}/actions/scrollSteps.js +0 -0
  49. package/{dist/assertions → lib/actions}/storageSteps.d.ts +0 -0
  50. package/{dist → lib}/assertions/buttonAndTextVisibilitySteps.d.ts +0 -0
  51. package/{dist → lib}/assertions/cookieSteps.d.ts +0 -0
  52. package/{dist → lib}/assertions/cookieSteps.js +0 -0
  53. package/{dist → lib}/assertions/elementSteps.d.ts +0 -0
  54. package/{dist → lib}/assertions/formInputSteps.d.ts +0 -0
  55. package/{dist → lib}/assertions/formInputSteps.js +0 -0
  56. package/{dist → lib}/assertions/interceptionRequestsSteps.d.ts +0 -0
  57. package/{dist → lib}/assertions/interceptionRequestsSteps.js +0 -0
  58. package/{dist → lib}/assertions/locationSteps.d.ts +0 -0
  59. package/{dist → lib}/assertions/roleTestIdSteps.d.ts +0 -0
  60. package/{dist → lib}/assertions/roleTestIdSteps.js +0 -0
  61. package/{dist → lib}/assertions/semanticSteps.d.ts +0 -0
  62. package/{dist/assertions/visualSteps.d.ts → lib/assertions/storageSteps.d.ts} +0 -0
  63. package/{dist → lib}/assertions/storageSteps.js +1 -1
  64. /package/{dist/custom_setups/loginHooks.d.ts → lib/assertions/visualSteps.d.ts} +0 -0
  65. /package/{dist/helpers/hooks.d.ts → lib/custom_setups/loginHooks.d.ts} +0 -0
  66. /package/{dist → lib}/helpers/checkPeerDeps.d.ts +0 -0
  67. /package/{dist → lib}/helpers/checkPeerDeps.js +0 -0
  68. /package/{dist → lib}/helpers/compareSnapshots.d.ts +0 -0
  69. /package/{dist → lib}/helpers/compareSnapshots.js +0 -0
  70. /package/{dist/iframes/frames.d.ts → lib/helpers/hooks.d.ts} +0 -0
  71. /package/{dist → lib}/helpers/utils/fakerUtils.d.ts +0 -0
  72. /package/{dist → lib}/helpers/utils/fakerUtils.js +0 -0
  73. /package/{dist → lib}/iframes/frames.js +0 -0
  74. /package/{dist → lib}/register.d.ts +0 -0
@@ -4,8 +4,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const cucumber_1 = require("@cucumber/cucumber");
7
+ const test_1 = require("@playwright/test");
7
8
  const dayjs_1 = __importDefault(require("dayjs"));
8
9
  const optionsUtils_1 = require("../helpers/utils/optionsUtils");
10
+ const resolveUtils_1 = require("../helpers/utils/resolveUtils");
9
11
  //
10
12
  // Timers
11
13
  //
@@ -38,16 +40,18 @@ const optionsUtils_1 = require("../helpers/utils/optionsUtils");
38
40
  (0, cucumber_1.When)(/^I wait (\d+) second[s]?$/, async function (seconds) {
39
41
  await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
40
42
  });
41
- (0, cucumber_1.When)("I wait {int} milliseconds", async function (ms) {
43
+ (0, cucumber_1.When)(/^I wait (\d+) millisecond[s]?$/, async function (ms) {
42
44
  await new Promise((res) => setTimeout(res, ms));
43
45
  });
46
+ (0, cucumber_1.When)("I set step timeout to {int} ms", function (timeoutMs) {
47
+ (0, cucumber_1.setDefaultTimeout)(timeoutMs);
48
+ this.log?.(`⏱️ Timeout set to ${timeoutMs}ms`);
49
+ });
44
50
  //
45
51
  // Events
46
52
  //
47
53
  (0, cucumber_1.When)(/^I trigger "(.*)" event on "([^"]+)"$/, async function (eventType, selector) {
48
- await this.page
49
- .locator(selector)
50
- .evaluate((el, type) => {
54
+ await this.page.locator(selector).evaluate((el, type) => {
51
55
  const event = new Event(type, {
52
56
  bubbles: true,
53
57
  cancelable: true,
@@ -73,8 +77,8 @@ const optionsUtils_1 = require("../helpers/utils/optionsUtils");
73
77
  //
74
78
  // Debugging / Logging
75
79
  //
76
- (0, cucumber_1.When)(/^I log "(.*)"$/, async function (_, message) {
77
- console.log(`[LOG]: ${message}`);
80
+ (0, cucumber_1.When)("I log {string}", async function (message) {
81
+ this.log(message);
78
82
  });
79
83
  (0, cucumber_1.When)(/^I debug$/, async function () {
80
84
  debugger;
@@ -163,9 +167,7 @@ const validUnits = [
163
167
  //
164
168
  (0, cucumber_1.When)("I switch to iframe with selector {string}", async function (selector) {
165
169
  const frameLocator = this.page.frameLocator(selector);
166
- await frameLocator
167
- .locator("body")
168
- .waitFor({ state: "visible", timeout: 10000 });
170
+ await frameLocator.locator("body").waitFor({ state: "visible", timeout: 10000 });
169
171
  this.frame = frameLocator;
170
172
  this.log?.(`🪟 Switched to iframe: ${selector}`);
171
173
  });
@@ -197,9 +199,7 @@ function toOrdinal(n) {
197
199
  async function getReadableLabel(el) {
198
200
  try {
199
201
  const tag = await el.evaluate((el) => el.tagName.toLowerCase());
200
- return tag === "input"
201
- ? await el.inputValue()
202
- : (await el.innerText()).trim();
202
+ return tag === "input" ? await el.inputValue() : (await el.innerText()).trim();
203
203
  }
204
204
  catch {
205
205
  return "(unknown)";
@@ -270,3 +270,51 @@ const locatorActions = {
270
270
  await this.element.press(key);
271
271
  this.log?.(`🎹 Pressed {${key}}`);
272
272
  });
273
+ (0, cucumber_1.When)(/^I set viewport to "([^"]+)"(?: and "([^"]+)")?$/, async function (deviceInput, orientation) {
274
+ const normalizedDevice = (0, resolveUtils_1.normalizeDeviceName)(deviceInput);
275
+ if (!normalizedDevice) {
276
+ throw new Error(`🚫 Unknown device: "${deviceInput}"`);
277
+ }
278
+ const baseDevice = test_1.devices[normalizedDevice];
279
+ if (!baseDevice) {
280
+ throw new Error(`🚫 Device not found: "${normalizedDevice}"`);
281
+ }
282
+ const isLandscape = orientation?.toLowerCase() === "landscape";
283
+ const deviceSettings = isLandscape
284
+ ? baseDevice.landscape
285
+ ? baseDevice.landscape
286
+ : {
287
+ ...baseDevice,
288
+ isMobile: true,
289
+ viewport: { ...baseDevice.viewport, isLandscape: true },
290
+ }
291
+ : baseDevice;
292
+ // Close current context if needed
293
+ if (this.context) {
294
+ await this.context.close();
295
+ }
296
+ this.context = await this.browser.newContext(deviceSettings);
297
+ this.page = await this.context.newPage();
298
+ this.log?.(`📱 Set viewport to ${normalizedDevice}${isLandscape ? " in landscape" : ""}`);
299
+ });
300
+ (0, cucumber_1.When)("I set viewport to {int}px by {int}px", async function (width, height) {
301
+ // Close existing context
302
+ if (this.context) {
303
+ await this.context.close();
304
+ }
305
+ // Recreate new context with the desired viewport
306
+ this.context = await this.browser.newContext({
307
+ viewport: { width, height },
308
+ });
309
+ this.page = await this.context.newPage();
310
+ this.log?.(`🖥️ Set viewport to ${width}x${height}`);
311
+ });
312
+ // Dynamic Playwright Config Setters (for page-only config)
313
+ (0, cucumber_1.When)('I set Playwright config "{word}" to {string}', async function (key, value) {
314
+ this.page[key] = value;
315
+ });
316
+ (0, cucumber_1.When)("I set Playwright config", async function (table) {
317
+ for (const [key, value] of table.rows()) {
318
+ this.page[key] = value;
319
+ }
320
+ });
@@ -1,6 +1,11 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  // e2e/step_definitions/common/actions/storageSteps.ts
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
4
9
  const cucumber_1 = require("@cucumber/cucumber");
5
10
  (0, cucumber_1.When)("I clear all local storage", async function () {
6
11
  await this.page.evaluate(() => localStorage.clear());
@@ -32,15 +37,15 @@ const cucumber_1 = require("@cucumber/cucumber");
32
37
  (0, cucumber_1.When)("I set local storage item {string} to {string}", async function (key, value) {
33
38
  await this.page.evaluate(([k, v]) => localStorage.setItem(k, v), [key, value]);
34
39
  });
35
- // When("I clear session storage", async function (this: CustomWorld) {
36
- // await this.page.evaluate(() => sessionStorage.clear());
37
- // });
40
+ (0, cucumber_1.When)("I set session storage item {string} to {string}", async function (key, value) {
41
+ await this.page.evaluate(([k, v]) => sessionStorage.setItem(k, v), [key, value]);
42
+ });
38
43
  (0, cucumber_1.When)("I clear local storage", async function () {
39
44
  await this.page.evaluate(() => localStorage.clear());
40
45
  });
41
46
  (0, cucumber_1.When)("I store input text as {string}", async function (alias) {
42
47
  const activeElementHandle = await this.page.evaluateHandle(() => document.activeElement);
43
- const tagName = await activeElementHandle.evaluate((el) => el ? el.tagName.toLowerCase() : "");
48
+ const tagName = await activeElementHandle.evaluate((el) => (el ? el.tagName.toLowerCase() : ""));
44
49
  if (tagName !== "input" && tagName !== "textarea") {
45
50
  throw new Error(`Active element is not an input or textarea (found: ${tagName})`);
46
51
  }
@@ -48,3 +53,20 @@ const cucumber_1 = require("@cucumber/cucumber");
48
53
  this.data[alias] = value;
49
54
  this.log?.(`📥 Stored value from input as "${alias}": ${value}`);
50
55
  });
56
+ (0, cucumber_1.When)("I clear session {string}", async function (fileName) {
57
+ const baseDir = this.parameters?.artifactDir || process.env.TEST_ARTIFACT_DIR || "test-artifacts";
58
+ const fullPath = path_1.default.resolve(baseDir, "auth-cookies", fileName);
59
+ try {
60
+ if (fs_1.default.existsSync(fullPath)) {
61
+ fs_1.default.unlinkSync(fullPath);
62
+ this.log?.(`🗑️ Session file deleted: ${fullPath}`);
63
+ }
64
+ else {
65
+ this.log?.(`ℹ️ Session file not found, nothing to delete: ${fullPath}`);
66
+ }
67
+ }
68
+ catch (err) {
69
+ this.log?.(`❌ Failed to delete session file: ${err.message}`);
70
+ throw err;
71
+ }
72
+ });
@@ -14,9 +14,7 @@ const fakerUtils_1 = require("../helpers/utils/fakerUtils");
14
14
  * THEN: I see button "Submit"
15
15
  */
16
16
  (0, cucumber_1.Then)(/^I see button "(.*)"$/, async function (rawText) {
17
- let buttonText = rawText.startsWith("@")
18
- ? this.data[rawText.slice(1)]
19
- : rawText;
17
+ let buttonText = rawText.startsWith("@") ? this.data[rawText.slice(1)] : rawText;
20
18
  if (!buttonText) {
21
19
  throw new Error(`No value found for alias: "${rawText}"`);
22
20
  }
@@ -31,9 +29,7 @@ const fakerUtils_1 = require("../helpers/utils/fakerUtils");
31
29
  * THEN: I do not see button "Cancel"
32
30
  */
33
31
  (0, cucumber_1.Then)(/^I do not see button "(.*)"$/, async function (rawText) {
34
- let buttonText = rawText.startsWith("@")
35
- ? this.data[rawText.slice(1)]
36
- : rawText;
32
+ let buttonText = rawText.startsWith("@") ? this.data[rawText.slice(1)] : rawText;
37
33
  if (!buttonText) {
38
34
  throw new Error(`No value found for alias: "${rawText}"`);
39
35
  }
@@ -59,20 +55,6 @@ const fakerUtils_1 = require("../helpers/utils/fakerUtils");
59
55
  await locator.waitFor({ state: "visible", timeout: 5000 });
60
56
  this.log?.(`✅ Verified text visible: ${expected}`);
61
57
  });
62
- /**
63
- * THEN: I do not see text "Error"
64
- */
65
- (0, cucumber_1.Then)("I do not see text {string}", async function (text) {
66
- await this.page.waitForLoadState("networkidle");
67
- const locator = this.page.locator(`:has-text("${text}")`);
68
- const count = await locator.count();
69
- for (let i = 0; i < count; i++) {
70
- const item = locator.nth(i);
71
- if (await item.isVisible()) {
72
- throw new Error(`Text "${text}" is visible but should not be`);
73
- }
74
- }
75
- });
76
58
  /**
77
59
  * THEN: I see visible text "Dashboard"
78
60
  */
@@ -151,9 +133,7 @@ const fakerUtils_1 = require("../helpers/utils/fakerUtils");
151
133
  });
152
134
  (0, cucumber_1.Then)("I see button {string} is disabled", async function (rawText) {
153
135
  // Resolve alias
154
- let buttonText = rawText.startsWith("@")
155
- ? this.data[rawText.slice(1)]
156
- : rawText;
136
+ let buttonText = rawText.startsWith("@") ? this.data[rawText.slice(1)] : rawText;
157
137
  if (!buttonText) {
158
138
  throw new Error(`No value found for alias: "${rawText}"`);
159
139
  }
@@ -59,6 +59,12 @@ const test_1 = require("@playwright/test");
59
59
  const el = this.page.locator(selector);
60
60
  await (0, test_1.expect)(el).toHaveAttribute(attribute, expected);
61
61
  });
62
+ (0, cucumber_1.Then)('I see element attribute "{word}" equals {string}', async function (attr, expected) {
63
+ if (!this.element) {
64
+ throw new Error("No element is currently selected. Use a 'find' step before asserting.");
65
+ }
66
+ await (0, test_1.expect)(this.element).toHaveAttribute(attr, expected);
67
+ });
62
68
  (0, cucumber_1.Then)("I see element has attribute {string}", async function (attr) {
63
69
  if (!this.element)
64
70
  throw new Error("No element in context");
@@ -66,7 +72,7 @@ const test_1 = require("@playwright/test");
66
72
  if (value === null)
67
73
  throw new Error(`Attribute "${attr}" not found`);
68
74
  });
69
- (0, cucumber_1.Then)("I see element attribute {string} contains {string}", async function (attr, part) {
75
+ (0, cucumber_1.Then)('I see element attribute "{word}" contains {string}', async function (attr, part) {
70
76
  if (!this.element)
71
77
  throw new Error("No element in context");
72
78
  const value = await this.element.getAttribute(attr);
@@ -69,3 +69,19 @@ const test_1 = require("@playwright/test");
69
69
  throw new Error(`Search does not contain "${part}". Got: "${search}"`);
70
70
  }
71
71
  });
72
+ (0, cucumber_1.Then)("I see location", async function (table) {
73
+ const location = await this.page.evaluate(() => ({
74
+ href: window.location.href,
75
+ origin: window.location.origin,
76
+ protocol: window.location.protocol,
77
+ host: window.location.host,
78
+ hostname: window.location.hostname,
79
+ port: window.location.port,
80
+ pathname: window.location.pathname,
81
+ search: window.location.search,
82
+ hash: window.location.hash,
83
+ }));
84
+ for (const [key, expected] of table.rows()) {
85
+ (0, test_1.expect)(location[key]).toBe(expected);
86
+ }
87
+ });
@@ -1,13 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const cucumber_1 = require("@cucumber/cucumber");
4
+ const test_1 = require("@playwright/test");
5
+ const optionsUtils_1 = require("../helpers/utils/optionsUtils");
4
6
  //
5
7
  // 🧠 HEADINGS
6
8
  //
7
9
  (0, cucumber_1.Then)("I see heading {string}", async function (text) {
8
- const heading = await this.page
9
- .locator("h1, h2, h3, h4, h5, h6", { hasText: text })
10
- .first();
10
+ const heading = await this.page.locator("h1, h2, h3, h4, h5, h6", { hasText: text }).first();
11
11
  if (!(await heading.isVisible())) {
12
12
  throw new Error(`Heading "${text}" not found or not visible`);
13
13
  }
@@ -52,3 +52,16 @@ const cucumber_1 = require("@cucumber/cucumber");
52
52
  throw new Error(`Link "${text}" is visible but should not be`);
53
53
  }
54
54
  });
55
+ (0, cucumber_1.Then)("I count {int} element", async function (count) {
56
+ const locator = this.currentLocator ?? this.page.locator("*");
57
+ await (0, test_1.expect)(locator).toHaveCount(count);
58
+ });
59
+ //document title assertions
60
+ (0, cucumber_1.Then)("I see document title {string}", async function (expected, table) {
61
+ const options = (0, optionsUtils_1.parseExpectOptions)(table);
62
+ await (0, test_1.expect)(this.page).toHaveTitle(expected, options);
63
+ });
64
+ (0, cucumber_1.Then)("I see document title contains {string}", async function (substring, table) {
65
+ const options = (0, optionsUtils_1.parseExpectOptions)(table);
66
+ await (0, test_1.expect)(this.page).toHaveTitle(new RegExp(substring, "i"), options);
67
+ });
@@ -3,12 +3,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const cucumber_1 = require("@cucumber/cucumber");
7
- const pixelmatch_1 = __importDefault(require("pixelmatch"));
8
- const pngjs_1 = require("pngjs");
9
6
  const fs_1 = __importDefault(require("fs"));
10
7
  const path_1 = __importDefault(require("path"));
8
+ const cucumber_1 = require("@cucumber/cucumber");
11
9
  const test_1 = require("@playwright/test");
10
+ const pixelmatch_1 = __importDefault(require("pixelmatch"));
11
+ const pngjs_1 = require("pngjs");
12
12
  const BASELINE_DIR = path_1.default.resolve("e2e/snapshots/baseline");
13
13
  const CURRENT_DIR = path_1.default.resolve("e2e/snapshots/current");
14
14
  const DIFF_DIR = path_1.default.resolve("e2e/snapshots/diff");
@@ -36,7 +36,9 @@ function getSnapshotPaths(name) {
36
36
  const current = pngjs_1.PNG.sync.read(fs_1.default.readFileSync(paths.current));
37
37
  const { width, height } = baseline;
38
38
  const diff = new pngjs_1.PNG({ width, height });
39
- const pixelDiff = (0, pixelmatch_1.default)(baseline.data, current.data, diff.data, width, height, { threshold: 0.1 });
39
+ const pixelDiff = (0, pixelmatch_1.default)(baseline.data, current.data, diff.data, width, height, {
40
+ threshold: 0.1,
41
+ });
40
42
  if (pixelDiff > 0) {
41
43
  fs_1.default.writeFileSync(paths.diff, pngjs_1.PNG.sync.write(diff));
42
44
  this.log?.(`❌ Visual mismatch detected, diff: ${paths.diff}`);
@@ -63,7 +65,9 @@ function getSnapshotPaths(name) {
63
65
  }
64
66
  const { width, height } = baseline;
65
67
  const diff = new pngjs_1.PNG({ width, height });
66
- const pixelDiff = (0, pixelmatch_1.default)(baseline.data, current.data, diff.data, width, height, { threshold: 0.1 });
68
+ const pixelDiff = (0, pixelmatch_1.default)(baseline.data, current.data, diff.data, width, height, {
69
+ threshold: 0.1,
70
+ });
67
71
  if (pixelDiff > 0) {
68
72
  fs_1.default.writeFileSync(paths.diff, pngjs_1.PNG.sync.write(diff));
69
73
  this.log?.(`⚠️ Snapshot mismatch: ${alias}`);
@@ -0,0 +1,113 @@
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
+ // Step 2: Save current context as session
27
+ (0, cucumber_1.When)(/^I save session as "([^"]+)"$/, async function (sessionName) {
28
+ const sessionPath = (0, resolveUtils_1.resolveSessionPath)(this, sessionName);
29
+ const cookies = await this.context.cookies();
30
+ const [localStorageData, sessionStorageData] = await this.page.evaluate(() => {
31
+ const toPairs = (store) => Object.entries(store);
32
+ return [
33
+ [{ origin: location.origin, values: toPairs(localStorage) }],
34
+ [{ origin: location.origin, values: toPairs(sessionStorage) }],
35
+ ];
36
+ });
37
+ const sessionData = {
38
+ cookies,
39
+ localStorage: localStorageData,
40
+ sessionStorage: sessionStorageData,
41
+ };
42
+ try {
43
+ fs_1.default.writeFileSync(sessionPath, JSON.stringify(sessionData, null, 2));
44
+ this.log?.(`💾 Saved session as "${sessionName}"`);
45
+ }
46
+ catch (err) {
47
+ this.log?.(`❌ Failed to save session "${sessionName}": ${err.message}`);
48
+ }
49
+ });
50
+ // Step 3: Remove a session
51
+ (0, cucumber_1.When)("I clear session {string}", function (sessionName) {
52
+ const sessionPath = (0, resolveUtils_1.resolveSessionPath)(this, sessionName);
53
+ if (fs_1.default.existsSync(sessionPath)) {
54
+ fs_1.default.unlinkSync(sessionPath);
55
+ this.log?.(`🧹 Cleared session: ${sessionPath}`);
56
+ }
57
+ else {
58
+ this.log?.(`⚠️ Session not found: ${sessionPath}`);
59
+ }
60
+ });
61
+ (0, cucumber_1.When)(/^I restore session cookies "([^"]+)"(?: with reload "(true|false)")?$/, async function (sessionName, reload = "true") {
62
+ const sessionPath = (0, resolveUtils_1.resolveSessionPath)(this, sessionName);
63
+ if (!fs_1.default.existsSync(sessionPath)) {
64
+ this.log?.(`❌ Session file not found: ${sessionPath}`);
65
+ return;
66
+ }
67
+ const sessionData = JSON.parse(fs_1.default.readFileSync(sessionPath, "utf-8"));
68
+ const { cookies = [], localStorage = [], sessionStorage = [] } = sessionData;
69
+ try {
70
+ // Clear & set cookies
71
+ if (cookies.length) {
72
+ const existing = await this.context.cookies();
73
+ if (existing.length)
74
+ await this.context.clearCookies();
75
+ await this.context.addCookies(cookies);
76
+ this.log?.(`🍪 Cookies restored from "${sessionName}"`);
77
+ }
78
+ // Apply storage into page context
79
+ await this.page.goto("about:blank");
80
+ if (localStorage.length > 0) {
81
+ for (const entry of localStorage) {
82
+ await this.page.addInitScript(([origin, values]) => {
83
+ if (window.origin === origin) {
84
+ for (const [key, val] of values) {
85
+ localStorage.setItem(key, val);
86
+ }
87
+ }
88
+ }, [entry.origin, entry.values]);
89
+ }
90
+ this.log?.("📦 localStorage restored");
91
+ }
92
+ if (sessionStorage.length > 0) {
93
+ for (const entry of sessionStorage) {
94
+ await this.page.addInitScript(([origin, values]) => {
95
+ if (window.origin === origin) {
96
+ for (const [key, val] of values) {
97
+ sessionStorage.setItem(key, val);
98
+ }
99
+ }
100
+ }, [entry.origin, entry.values]);
101
+ }
102
+ this.log?.("🗄️ sessionStorage restored");
103
+ }
104
+ // Final reload to apply context if requested
105
+ if (reload !== "false") {
106
+ await this.page.reload();
107
+ this.log?.("🔄 Page reloaded to apply restored session");
108
+ }
109
+ }
110
+ catch (err) {
111
+ this.log?.(`❌ Error restoring session: ${err.message}`);
112
+ }
113
+ });
@@ -0,0 +1,210 @@
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,3 +1,4 @@
1
1
  export * from "./fakerUtils";
2
2
  export * from "./optionsUtils";
3
3
  export * from "./resolveUtils";
4
+ export * from "./sessionUtils";