playwright-cucumber-ts-steps 1.0.1 โ†’ 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/README.md +270 -244
  2. package/dist/backend/actions/formTable.js +34 -0
  3. package/dist/backend/actions/index.js +5 -0
  4. package/dist/backend/actions/interactions.js +23 -0
  5. package/dist/backend/actions/navigation.js +19 -0
  6. package/dist/backend/api/assertions.js +26 -0
  7. package/dist/backend/api/index.js +5 -0
  8. package/dist/backend/api/mock.js +75 -0
  9. package/dist/backend/api/requests.js +86 -0
  10. package/dist/backend/api/state.js +15 -0
  11. package/dist/backend/assertions/expectVisible.js +8 -0
  12. package/dist/backend/assertions/index.js +5 -0
  13. package/dist/backend/assertions/pageState.js +25 -0
  14. package/dist/backend/assertions/text.js +20 -0
  15. package/dist/backend/assertions/visibility.js +20 -0
  16. package/dist/backend/auth/index.js +71 -0
  17. package/dist/backend/db/index.js +6 -0
  18. package/dist/backend/db/state.js +24 -0
  19. package/dist/backend/db/steps.js +43 -0
  20. package/dist/backend/elements/alerts.js +21 -0
  21. package/dist/backend/elements/forms.js +59 -0
  22. package/dist/backend/elements/frames.js +25 -0
  23. package/dist/backend/elements/index.js +5 -0
  24. package/dist/core/registry.js +20 -0
  25. package/dist/core/runner.js +151 -0
  26. package/dist/index.js +10 -0
  27. package/dist/reporting/index.js +43 -0
  28. package/package.json +19 -101
  29. package/LICENSE +0 -21
  30. package/lib/actions/clickSteps.d.ts +0 -251
  31. package/lib/actions/clickSteps.js +0 -415
  32. package/lib/actions/cookieSteps.d.ts +0 -18
  33. package/lib/actions/cookieSteps.js +0 -93
  34. package/lib/actions/debugSteps.d.ts +0 -14
  35. package/lib/actions/debugSteps.js +0 -23
  36. package/lib/actions/elementFindSteps.d.ts +0 -668
  37. package/lib/actions/elementFindSteps.js +0 -931
  38. package/lib/actions/fillFormSteps.d.ts +0 -69
  39. package/lib/actions/fillFormSteps.js +0 -237
  40. package/lib/actions/index.d.ts +0 -11
  41. package/lib/actions/index.js +0 -28
  42. package/lib/actions/inputSteps.d.ts +0 -218
  43. package/lib/actions/inputSteps.js +0 -343
  44. package/lib/actions/interceptionSteps.d.ts +0 -169
  45. package/lib/actions/interceptionSteps.js +0 -291
  46. package/lib/actions/miscSteps.d.ts +0 -645
  47. package/lib/actions/miscSteps.js +0 -1061
  48. package/lib/actions/mouseSteps.d.ts +0 -143
  49. package/lib/actions/mouseSteps.js +0 -234
  50. package/lib/actions/scrollSteps.d.ts +0 -82
  51. package/lib/actions/scrollSteps.js +0 -123
  52. package/lib/actions/storageSteps.d.ts +0 -174
  53. package/lib/actions/storageSteps.js +0 -292
  54. package/lib/assertions/buttonAndTextVisibilitySteps.d.ts +0 -245
  55. package/lib/assertions/buttonAndTextVisibilitySteps.js +0 -401
  56. package/lib/assertions/cookieSteps.d.ts +0 -75
  57. package/lib/assertions/cookieSteps.js +0 -113
  58. package/lib/assertions/elementSteps.d.ts +0 -264
  59. package/lib/assertions/elementSteps.js +0 -388
  60. package/lib/assertions/formInputSteps.d.ts +0 -248
  61. package/lib/assertions/formInputSteps.js +0 -350
  62. package/lib/assertions/index.d.ts +0 -10
  63. package/lib/assertions/index.js +0 -27
  64. package/lib/assertions/interceptionRequestsSteps.d.ts +0 -353
  65. package/lib/assertions/interceptionRequestsSteps.js +0 -593
  66. package/lib/assertions/locationSteps.d.ts +0 -217
  67. package/lib/assertions/locationSteps.js +0 -310
  68. package/lib/assertions/roleTestIdSteps.d.ts +0 -159
  69. package/lib/assertions/roleTestIdSteps.js +0 -221
  70. package/lib/assertions/semanticSteps.d.ts +0 -176
  71. package/lib/assertions/semanticSteps.js +0 -252
  72. package/lib/assertions/storageSteps.d.ts +0 -149
  73. package/lib/assertions/storageSteps.js +0 -210
  74. package/lib/assertions/visualSteps.d.ts +0 -74
  75. package/lib/assertions/visualSteps.js +0 -209
  76. package/lib/custom_setups/loginHooks.d.ts +0 -1
  77. package/lib/custom_setups/loginHooks.js +0 -130
  78. package/lib/helpers/checkPeerDeps.d.ts +0 -1
  79. package/lib/helpers/checkPeerDeps.js +0 -19
  80. package/lib/helpers/compareSnapshots.d.ts +0 -6
  81. package/lib/helpers/compareSnapshots.js +0 -20
  82. package/lib/helpers/hooks.d.ts +0 -1
  83. package/lib/helpers/hooks.js +0 -210
  84. package/lib/helpers/utils/fakerUtils.d.ts +0 -1
  85. package/lib/helpers/utils/fakerUtils.js +0 -60
  86. package/lib/helpers/utils/index.d.ts +0 -4
  87. package/lib/helpers/utils/index.js +0 -20
  88. package/lib/helpers/utils/optionsUtils.d.ts +0 -24
  89. package/lib/helpers/utils/optionsUtils.js +0 -88
  90. package/lib/helpers/utils/resolveUtils.d.ts +0 -6
  91. package/lib/helpers/utils/resolveUtils.js +0 -72
  92. package/lib/helpers/utils/sessionUtils.d.ts +0 -3
  93. package/lib/helpers/utils/sessionUtils.js +0 -40
  94. package/lib/helpers/world.d.ts +0 -34
  95. package/lib/helpers/world.js +0 -110
  96. package/lib/iframes/frames.d.ts +0 -1
  97. package/lib/iframes/frames.js +0 -11
  98. package/lib/index.d.ts +0 -10
  99. package/lib/index.js +0 -28
  100. package/lib/register.d.ts +0 -1
  101. package/lib/register.js +0 -6
  102. package/src/actions/clickSteps.ts +0 -429
  103. package/src/actions/cookieSteps.ts +0 -95
  104. package/src/actions/debugSteps.ts +0 -21
  105. package/src/actions/elementFindSteps.ts +0 -961
  106. package/src/actions/fillFormSteps.ts +0 -270
  107. package/src/actions/index.ts +0 -12
  108. package/src/actions/inputSteps.ts +0 -354
  109. package/src/actions/interceptionSteps.ts +0 -325
  110. package/src/actions/miscSteps.ts +0 -1144
  111. package/src/actions/mouseSteps.ts +0 -256
  112. package/src/actions/scrollSteps.ts +0 -122
  113. package/src/actions/storageSteps.ts +0 -308
  114. package/src/assertions/buttonAndTextVisibilitySteps.ts +0 -436
  115. package/src/assertions/cookieSteps.ts +0 -131
  116. package/src/assertions/elementSteps.ts +0 -432
  117. package/src/assertions/formInputSteps.ts +0 -377
  118. package/src/assertions/index.ts +0 -11
  119. package/src/assertions/interceptionRequestsSteps.ts +0 -640
  120. package/src/assertions/locationSteps.ts +0 -315
  121. package/src/assertions/roleTestIdSteps.ts +0 -254
  122. package/src/assertions/semanticSteps.ts +0 -267
  123. package/src/assertions/storageSteps.ts +0 -250
  124. package/src/assertions/visualSteps.ts +0 -275
  125. package/src/custom_setups/loginHooks.ts +0 -154
  126. package/src/helpers/checkPeerDeps.ts +0 -19
  127. package/src/helpers/compareSnapshots.ts +0 -35
  128. package/src/helpers/hooks.ts +0 -212
  129. package/src/helpers/utils/fakerUtils.ts +0 -64
  130. package/src/helpers/utils/index.ts +0 -4
  131. package/src/helpers/utils/optionsUtils.ts +0 -104
  132. package/src/helpers/utils/resolveUtils.ts +0 -74
  133. package/src/helpers/utils/sessionUtils.ts +0 -36
  134. package/src/helpers/world.ts +0 -119
  135. package/src/iframes/frames.ts +0 -15
  136. package/src/index.ts +0 -18
  137. package/src/register.ts +0 -4
@@ -1,275 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { Then } from "@cucumber/cucumber";
4
- import { expect } from "@playwright/test";
5
- import pixelmatch from "pixelmatch"; // Ensure pixelmatch is installed: npm install pixelmatch
6
- import { PNG } from "pngjs"; // Ensure pngjs is installed: npm install pngjs
7
- import type { CustomWorld } from "../helpers/world"; // Assuming this path is correct
8
-
9
- // --- Configuration for Snapshot Directories ---
10
- // It's good practice to make these configurable (e.g., via world.config or env variables)
11
- // For now, keeping them as resolved constants as per your original code.
12
- const SNAPSHOTS_BASE_DIR = path.resolve("e2e/snapshots"); // Base directory for all snapshots
13
- const BASELINE_DIR = path.join(SNAPSHOTS_BASE_DIR, "baseline");
14
- const CURRENT_DIR = path.join(SNAPSHOTS_BASE_DIR, "current");
15
- const DIFF_DIR = path.join(SNAPSHOTS_BASE_DIR, "diff");
16
-
17
- // Helper function to generate standardized snapshot paths
18
- function getSnapshotPaths(name: string) {
19
- // Sanitize the name for use in filenames
20
- const safeName = name.replace(/[^a-z0-9]/gi, "_").toLowerCase();
21
- return {
22
- baseline: path.join(BASELINE_DIR, `${safeName}.png`),
23
- current: path.join(CURRENT_DIR, `${safeName}.png`),
24
- diff: path.join(DIFF_DIR, `${safeName}.diff.png`),
25
- };
26
- }
27
-
28
- // Helper to ensure all necessary directories exist
29
- function ensureSnapshotDirs() {
30
- fs.mkdirSync(BASELINE_DIR, { recursive: true });
31
- fs.mkdirSync(CURRENT_DIR, { recursive: true });
32
- fs.mkdirSync(DIFF_DIR, { recursive: true });
33
- }
34
-
35
- // ===================================================================================
36
- // VISUAL REGRESSION ASSERTIONS: PAGE SNAPSHOTS
37
- // ===================================================================================
38
-
39
- /**
40
- * Asserts that the current page's visual appearance matches a named baseline snapshot.
41
- * If no baseline exists for the given name, a new one is created from the current page.
42
- * Differences between current and baseline snapshots are highlighted in a 'diff' image.
43
- *
44
- * ```gherkin
45
- * Then I should see the page matches the snapshot {string}
46
- * ```
47
- *
48
- * @param name - A unique name for the snapshot (e.g., "homepage", "product-details-page").
49
- *
50
- * @example
51
- * Then I should see the page matches the snapshot "homepage"
52
- *
53
- * @remarks
54
- * This is a core step for visual regression testing.
55
- * 1. Takes a screenshot of the current page and saves it to the `current` directory.
56
- * 2. If a `baseline` snapshot does not exist, the `current` snapshot is copied to `baseline`,
57
- * and the test passes (a new baseline is established).
58
- * 3. If a `baseline` exists, it compares the `current` and `baseline` snapshots pixel by pixel
59
- * using `pixelmatch`.
60
- * 4. If a mismatch is detected (more than 0 differing pixels based on `threshold`), a `diff`
61
- * image is generated, and the test fails.
62
- *
63
- * All snapshots (baseline, current, diff) are stored in `e2e/snapshots/`.
64
- * Adjust `threshold` for sensitivity (0.1 means 10% difference in pixel color is allowed).
65
- * @category Visual Regression Steps
66
- */
67
- export async function Then_I_should_see_page_matches_snapshot(this: CustomWorld, name: string) {
68
- const { page } = this;
69
- const paths = getSnapshotPaths(name);
70
-
71
- ensureSnapshotDirs(); // Ensure directories exist before taking screenshot
72
-
73
- // Take current screenshot
74
- await page.screenshot({ path: paths.current, fullPage: true });
75
- this.log?.(`๐Ÿ“ธ Captured current snapshot: "${paths.current}".`);
76
-
77
- if (!fs.existsSync(paths.baseline)) {
78
- // If no baseline exists, create one from the current screenshot
79
- fs.copyFileSync(paths.current, paths.baseline);
80
- this.log?.(`โœจ Created new baseline snapshot: "${paths.baseline}".`);
81
- return; // Pass the test if a new baseline was created
82
- }
83
-
84
- // Load baseline and current images for comparison
85
- const baselineImg = PNG.sync.read(fs.readFileSync(paths.baseline));
86
- const currentImg = PNG.sync.read(fs.readFileSync(paths.current));
87
-
88
- // Ensure images have the same dimensions for comparison
89
- if (baselineImg.width !== currentImg.width || baselineImg.height !== currentImg.height) {
90
- fs.writeFileSync(
91
- paths.diff,
92
- PNG.sync.write(
93
- new PNG({
94
- width: Math.max(baselineImg.width, currentImg.width),
95
- height: Math.max(baselineImg.height, currentImg.height),
96
- })
97
- )
98
- );
99
- throw new Error(
100
- `Visual snapshot mismatch for "${name}": Dimensions differ! ` +
101
- `Baseline: ${baselineImg.width}x${baselineImg.height}, Current: ${currentImg.width}x${currentImg.height}. ` +
102
- `Diff image generated at "${paths.diff}".`
103
- );
104
- }
105
-
106
- const { width, height } = baselineImg;
107
- const diffImg = new PNG({ width, height });
108
-
109
- // Compare images pixel by pixel
110
- const pixelDiff = pixelmatch(
111
- baselineImg.data,
112
- currentImg.data,
113
- diffImg.data,
114
- width,
115
- height,
116
- { threshold: 0.1 } // Adjust threshold for sensitivity (0.1 means 10% difference in pixel color is allowed)
117
- );
118
-
119
- if (pixelDiff > 0) {
120
- // If differences found, write the diff image
121
- fs.writeFileSync(paths.diff, PNG.sync.write(diffImg));
122
- this.log?.(
123
- `โŒ Visual mismatch detected for "${name}". ${pixelDiff} pixels differ. Diff image: "${paths.diff}".`
124
- );
125
- }
126
-
127
- // Assert that no pixels differ
128
- expect(pixelDiff, `Visual snapshot "${name}" mismatch: ${pixelDiff} pixels differ.`).toBe(0);
129
- this.log?.(`โœ… Visual snapshot "${name}" matches baseline (0 pixels differ).`);
130
- }
131
- Then(
132
- "I should see the page matches the snapshot {string}",
133
- Then_I_should_see_page_matches_snapshot
134
- );
135
-
136
- // ===================================================================================
137
- // VISUAL REGRESSION: ELEMENT SNAPSHOT CAPTURE & MATCH
138
- // ===================================================================================
139
-
140
- /**
141
- * Captures a visual snapshot of a specific element identified by its selector and saves it under a given alias.
142
- * This snapshot is saved to the `current` directory and can later be compared against a baseline.
143
- *
144
- * ```gherkin
145
- * Then I capture a snapshot of the element {string} as {string}
146
- * ```
147
- *
148
- * @param selector - The CSS selector of the element to capture.
149
- * @param alias - A unique alias name for this element snapshot (e.g., "logo-image", "product-card").
150
- *
151
- * @example
152
- * Then I capture a snapshot of the element ".header .logo" as "logo-snapshot"
153
- * Then I capture a snapshot of the element "#user-profile" as "user-profile-widget"
154
- *
155
- * @remarks
156
- * This step is typically followed by {@link Then_the_snapshot_should_match_baseline | "Then the snapshot {string} should match baseline"}
157
- * to perform the actual visual comparison.
158
- * @category Visual Regression Steps
159
- */
160
- export async function Then_I_capture_element_snapshot_as_alias(
161
- this: CustomWorld,
162
- selector: string,
163
- alias: string
164
- ) {
165
- const elementLocator = this.getScope().locator(selector);
166
- ensureSnapshotDirs(); // Ensure directories exist
167
- const pathCurrent = path.join(CURRENT_DIR, `${alias}.png`);
168
-
169
- await elementLocator.screenshot({ path: pathCurrent });
170
- this.log?.(`๐Ÿ“ธ Captured snapshot of element "${selector}" saved as "${alias}".`);
171
- }
172
- Then(
173
- "I capture a snapshot of the element {string} as {string}",
174
- Then_I_capture_element_snapshot_as_alias
175
- );
176
-
177
- /**
178
- * Asserts that a previously captured named snapshot (of an element) matches its baseline.
179
- * If no baseline exists, a new one is created from the current snapshot.
180
- *
181
- * ```gherkin
182
- * Then The snapshot {string} should match baseline
183
- * ```
184
- *
185
- * @param alias - The unique alias name of the snapshot (as used in "Then I capture a snapshot...").
186
- *
187
- * @example
188
- * Then I capture a snapshot of the element ".logo" as "logo-snapshot"
189
- * Then The snapshot "logo-snapshot" should match baseline
190
- *
191
- * @remarks
192
- * This step is designed to be used after a step like
193
- * {@link Then_I_capture_element_snapshot_as_alias | "Then I capture a snapshot of the element {string} as {string}"}.
194
- * It performs the same comparison logic as `Then I should see the page matches the snapshot`,
195
- * but specifically for an element snapshot.
196
- * All snapshots (baseline, current, diff) are stored in `e2e/snapshots/`.
197
- * @category Visual Regression Steps
198
- */
199
- export async function Then_the_snapshot_should_match_baseline(this: CustomWorld, alias: string) {
200
- const paths = getSnapshotPaths(alias); // Get paths for baseline, current, diff based on alias
201
-
202
- ensureSnapshotDirs(); // Ensure directories exist
203
-
204
- // Check if the current snapshot file actually exists.
205
- // This is crucial because `Then I capture a snapshot` must have been run first.
206
- if (!fs.existsSync(paths.current)) {
207
- throw new Error(
208
- `Current snapshot file for alias "${alias}" not found at "${paths.current}".` +
209
- `Ensure "Then I capture a snapshot of the element {string} as {string}" was run successfully before this step.`
210
- );
211
- }
212
-
213
- // Load current image
214
- const currentImg = PNG.sync.read(fs.readFileSync(paths.current));
215
-
216
- let baselineImg: PNG | null = null;
217
- if (fs.existsSync(paths.baseline)) {
218
- // Load baseline image if it exists
219
- baselineImg = PNG.sync.read(fs.readFileSync(paths.baseline));
220
- }
221
-
222
- if (!baselineImg) {
223
- // If no baseline exists, create one from the current snapshot
224
- fs.copyFileSync(paths.current, paths.baseline);
225
- this.log?.(`โœจ Created new baseline for snapshot "${alias}": "${paths.baseline}".`);
226
- return; // Pass the test if a new baseline was created
227
- }
228
-
229
- // Ensure images have the same dimensions for comparison
230
- if (baselineImg.width !== currentImg.width || baselineImg.height !== currentImg.height) {
231
- fs.writeFileSync(
232
- paths.diff,
233
- PNG.sync.write(
234
- new PNG({
235
- width: Math.max(baselineImg.width, currentImg.width),
236
- height: Math.max(baselineImg.height, currentImg.height),
237
- })
238
- )
239
- );
240
- throw new Error(
241
- `Visual element snapshot mismatch for "${alias}": Dimensions differ! ` +
242
- `Baseline: ${baselineImg.width}x${baselineImg.height}, Current: ${currentImg.width}x${currentImg.height}. ` +
243
- `Diff image generated at "${paths.diff}".`
244
- );
245
- }
246
-
247
- const { width, height } = baselineImg;
248
- const diffImg = new PNG({ width, height });
249
-
250
- // Compare images pixel by pixel
251
- const pixelDiff = pixelmatch(
252
- baselineImg.data,
253
- currentImg.data,
254
- diffImg.data,
255
- width,
256
- height,
257
- { threshold: 0.1 } // Consistent threshold
258
- );
259
-
260
- if (pixelDiff > 0) {
261
- // If differences found, write the diff image
262
- fs.writeFileSync(paths.diff, PNG.sync.write(diffImg));
263
- this.log?.(
264
- `โŒ Visual mismatch detected for snapshot "${alias}". ${pixelDiff} pixels differ. Diff image: "${paths.diff}".`
265
- );
266
- }
267
-
268
- // Assert that no pixels differ
269
- expect(
270
- pixelDiff,
271
- `Visual element snapshot "${alias}" mismatch: ${pixelDiff} pixels differ.`
272
- ).toBe(0);
273
- this.log?.(`โœ… Visual element snapshot "${alias}" matches baseline (0 pixels differ).`);
274
- }
275
- Then("The snapshot {string} should match baseline", Then_the_snapshot_should_match_baseline);
@@ -1,154 +0,0 @@
1
- import fs from "fs";
2
- import { When } from "@cucumber/cucumber";
3
- import { resolveSessionPath } from "../helpers/utils/resolveUtils";
4
- import { CustomWorld } from "../helpers/world";
5
-
6
- // Step 1: Check and load existing session if valid
7
- When(
8
- "I login with a session data {string}",
9
- async function (this: CustomWorld, sessionName: string) {
10
- const sessionPath = resolveSessionPath(this, sessionName);
11
- this.data.sessionFile = sessionPath;
12
-
13
- if (fs.existsSync(sessionPath)) {
14
- try {
15
- await this.context?.addCookies(
16
- JSON.parse(fs.readFileSync(sessionPath, "utf-8")).cookies || []
17
- );
18
- this.log?.(`โœ… Loaded session from ${sessionPath}`);
19
- } catch (err) {
20
- this.log?.(`โš ๏ธ Failed to apply session: ${(err as Error).message}`);
21
- }
22
- } else {
23
- this.log?.(`โš ๏ธ Session file not found: ${sessionPath}`);
24
- }
25
- }
26
- );
27
-
28
- /**
29
- * @step
30
- * @description Saves the current browser context (cookies, localStorage, sessionStorage) as a session file.
31
- * @example
32
- * When I save session as "my-session"
33
- */
34
- When(/^I save session as "([^"]+)"$/, async function (this: CustomWorld, sessionName: string) {
35
- const sessionPath = resolveSessionPath(this, sessionName);
36
-
37
- const cookies = await this.context.cookies();
38
-
39
- const [localStorageData, sessionStorageData]: [
40
- { origin: string; values: [string, string][] }[],
41
- { origin: string; values: [string, string][] }[],
42
- ] = await this.page.evaluate(() => {
43
- const toPairs = (store: Storage): [string, string][] => Object.entries(store);
44
-
45
- return [
46
- [{ origin: location.origin, values: toPairs(localStorage) }],
47
- [{ origin: location.origin, values: toPairs(sessionStorage) }],
48
- ];
49
- });
50
-
51
- const sessionData = {
52
- cookies,
53
- localStorage: localStorageData,
54
- sessionStorage: sessionStorageData,
55
- };
56
-
57
- try {
58
- fs.writeFileSync(sessionPath, JSON.stringify(sessionData, null, 2));
59
- this.log?.(`๐Ÿ’พ Saved session as "${sessionName}"`);
60
- } catch (err) {
61
- this.log?.(`โŒ Failed to save session "${sessionName}": ${(err as Error).message}`);
62
- }
63
- });
64
-
65
- /**
66
- * @step
67
- * @description Removes a session file with the given name.
68
- * @example
69
- * When I clear session "my-session"
70
- */
71
- When("I clear session {string}", function (this: CustomWorld, sessionName: string) {
72
- const sessionPath = resolveSessionPath(this, sessionName);
73
- if (fs.existsSync(sessionPath)) {
74
- fs.unlinkSync(sessionPath);
75
- this.log?.(`๐Ÿงน Cleared session: ${sessionPath}`);
76
- } else {
77
- this.log?.(`โš ๏ธ Session not found: ${sessionPath}`);
78
- }
79
- });
80
-
81
- /**
82
- * @step
83
- * @description Restores cookies, localStorage, and sessionStorage from a session file. Optionally reloads the page.
84
- * @example
85
- * When I restore session cookies "my-session"
86
- * When I restore session cookies "my-session" with reload "false"
87
- */
88
- When(
89
- /^I restore session cookies "([^"]+)"(?: with reload "(true|false)")?$/,
90
- async function (this: CustomWorld, sessionName: string, reload = "true") {
91
- const sessionPath = resolveSessionPath(this, sessionName);
92
-
93
- if (!fs.existsSync(sessionPath)) {
94
- this.log?.(`โŒ Session file not found: ${sessionPath}`);
95
- return;
96
- }
97
-
98
- const sessionData = JSON.parse(fs.readFileSync(sessionPath, "utf-8"));
99
- const { cookies = [], localStorage = [], sessionStorage = [] } = sessionData;
100
-
101
- try {
102
- // Clear & set cookies
103
- if (cookies.length) {
104
- const existing = await this.context.cookies();
105
- if (existing.length) await this.context.clearCookies();
106
- await this.context.addCookies(cookies);
107
- this.log?.(`๐Ÿช Cookies restored from "${sessionName}"`);
108
- }
109
-
110
- // Apply storage into page context
111
- await this.page.goto("about:blank");
112
-
113
- if (localStorage.length > 0) {
114
- for (const entry of localStorage) {
115
- await this.page.addInitScript(
116
- ([origin, values]) => {
117
- if (window.origin === origin) {
118
- for (const [key, val] of values) {
119
- localStorage.setItem(key, val);
120
- }
121
- }
122
- },
123
- [entry.origin, entry.values]
124
- );
125
- }
126
- this.log?.("๐Ÿ“ฆ localStorage restored");
127
- }
128
-
129
- if (sessionStorage.length > 0) {
130
- for (const entry of sessionStorage) {
131
- await this.page.addInitScript(
132
- ([origin, values]) => {
133
- if (window.origin === origin) {
134
- for (const [key, val] of values) {
135
- sessionStorage.setItem(key, val);
136
- }
137
- }
138
- },
139
- [entry.origin, entry.values]
140
- );
141
- }
142
- this.log?.("๐Ÿ—„๏ธ sessionStorage restored");
143
- }
144
-
145
- // Final reload to apply context if requested
146
- if (reload !== "false") {
147
- await this.page.reload();
148
- this.log?.("๐Ÿ”„ Page reloaded to apply restored session");
149
- }
150
- } catch (err) {
151
- this.log?.(`โŒ Error restoring session: ${(err as Error).message}`);
152
- }
153
- }
154
- );
@@ -1,19 +0,0 @@
1
- export function checkPeerDependencies(dependencies: string[]) {
2
- const missing: string[] = [];
3
-
4
- for (const dep of dependencies) {
5
- try {
6
- require.resolve(dep);
7
- } catch {
8
- missing.push(dep);
9
- }
10
- }
11
-
12
- if (missing.length) {
13
- console.warn(
14
- `\nโŒ Missing peer dependencies: ${missing.join(", ")}` +
15
- `\nPlease install them in your project:\n\n` +
16
- `npm install --save-dev ${missing.join(" ")}\n`
17
- );
18
- }
19
- }
@@ -1,35 +0,0 @@
1
- import fs from "fs";
2
- import pixelmatch from "pixelmatch";
3
- import { PNG } from "pngjs";
4
-
5
- export function compareSnapshots({
6
- actualPath,
7
- baselinePath,
8
- diffPath,
9
- threshold = 0.1,
10
- }: {
11
- actualPath: string;
12
- baselinePath: string;
13
- diffPath: string;
14
- threshold?: number;
15
- }): number {
16
- const actual = PNG.sync.read(fs.readFileSync(actualPath));
17
- const baseline = PNG.sync.read(fs.readFileSync(baselinePath));
18
-
19
- if (actual.width !== baseline.width || actual.height !== baseline.height) {
20
- throw new Error("Snapshot size mismatch");
21
- }
22
-
23
- const diff = new PNG({ width: actual.width, height: actual.height });
24
- const numDiffPixels = pixelmatch(
25
- actual.data,
26
- baseline.data,
27
- diff.data,
28
- actual.width,
29
- actual.height,
30
- { threshold }
31
- );
32
-
33
- fs.writeFileSync(diffPath, PNG.sync.write(diff));
34
- return numDiffPixels;
35
- }
@@ -1,212 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import {
4
- Before,
5
- After,
6
- BeforeAll,
7
- AfterAll,
8
- ITestCaseHookParameter,
9
- setDefaultTimeout,
10
- } from "@cucumber/cucumber";
11
- import * as dotenv from "dotenv";
12
- import { chromium, devices, Browser, BrowserContextOptions } from "playwright";
13
- import { compareSnapshots } from "./compareSnapshots";
14
- import { CustomWorld } from "./world";
15
-
16
- // Set to 30 seconds
17
- setDefaultTimeout(30 * 1000);
18
- dotenv.config();
19
-
20
- let sharedBrowser: Browser;
21
-
22
- BeforeAll(async () => {
23
- sharedBrowser = await chromium.launch({
24
- headless: process.env.HEADLESS !== "false",
25
- });
26
- console.log("๐Ÿš€ Launched shared browser for all scenarios");
27
- });
28
-
29
- AfterAll(async () => {
30
- await sharedBrowser?.close();
31
- console.log("๐Ÿงน Closed shared browser after all scenarios");
32
- });
33
-
34
- Before(async function (this: CustomWorld, scenario: ITestCaseHookParameter) {
35
- const params = this.parameters || {};
36
- const ARTIFACT_DIR = params.artifactDir || process.env.TEST_ARTIFACT_DIR || "test-artifacts";
37
- const SCREENSHOT_DIR = path.resolve(ARTIFACT_DIR, "screenshots");
38
- const VIDEO_DIR = path.resolve(ARTIFACT_DIR, "videos");
39
- const TRACE_DIR = path.resolve(ARTIFACT_DIR, "traces");
40
- const SESSION_FILE = path.resolve(ARTIFACT_DIR, "auth-cookies", "session.json");
41
-
42
- this.data.artifactDir = ARTIFACT_DIR;
43
- this.data.screenshotDir = SCREENSHOT_DIR;
44
- this.data.videoDir = VIDEO_DIR;
45
- this.data.traceDir = TRACE_DIR;
46
- this.data.sessionFile = SESSION_FILE;
47
-
48
- // Modes: "false" | "fail" | "all"
49
- const traceMode = (params.enableTrace || process.env.ENABLE_TRACE || "false").toLowerCase();
50
- const screenshotMode = (
51
- params.enableScreenshots ||
52
- process.env.ENABLE_SCREENSHOTS ||
53
- "false"
54
- ).toLowerCase();
55
- const videoMode = (params.enableVideos || process.env.ENABLE_VIDEOS || "false").toLowerCase();
56
-
57
- this.data.traceMode = traceMode;
58
- this.data.screenshotMode = screenshotMode;
59
- this.data.videoMode = videoMode;
60
-
61
- const isMobileTag = scenario.pickle.tags.some((t) => t.name === "@mobile");
62
- const deviceName =
63
- params.device || process.env.MOBILE_DEVICE || (isMobileTag ? "iPhone 13 Pro" : null);
64
- const deviceSettings = deviceName ? devices[deviceName] : undefined;
65
-
66
- if (deviceName && !deviceSettings) {
67
- throw new Error(`๐Ÿšซ Invalid MOBILE_DEVICE: "${deviceName}" is not recognized by Playwright.`);
68
- }
69
-
70
- const isVisualTest =
71
- params.enableVisualTest ??
72
- (process.env.ENABLE_VISUAL_TEST === "true" ||
73
- scenario.pickle.tags.some((t) => t.name === "@visual"));
74
-
75
- this.data.enableVisualTest = isVisualTest;
76
- if (isVisualTest) process.env.VISUAL_TEST = "true";
77
-
78
- const contextOptions: BrowserContextOptions = {
79
- ...(videoMode !== "false" ? { recordVideo: { dir: VIDEO_DIR } } : {}),
80
- ...(deviceSettings || {}),
81
- };
82
-
83
- if (fs.existsSync(SESSION_FILE)) {
84
- contextOptions.storageState = SESSION_FILE;
85
- this.log?.("โœ… Reusing session from saved file.");
86
- }
87
-
88
- const context = await sharedBrowser.newContext(contextOptions);
89
- const page = await context.newPage();
90
-
91
- this.browser = sharedBrowser;
92
- this.context = context;
93
- this.page = page;
94
-
95
- if (traceMode !== "false") {
96
- await context.tracing.start({
97
- screenshots: true,
98
- snapshots: true,
99
- sources: true,
100
- });
101
- this.data.tracingStarted = true;
102
- this.log?.(`๐Ÿงช Tracing started (${traceMode})`);
103
- }
104
-
105
- if (deviceName) this.log?.(`๐Ÿ“ฑ Mobile emulation enabled (${deviceName})`);
106
- });
107
-
108
- After(async function (this: CustomWorld, scenario: ITestCaseHookParameter) {
109
- const name = scenario.pickle.name.replace(/[^a-z0-9]+/gi, "_").toLowerCase();
110
- const failed = scenario.result?.status === "FAILED";
111
- const mode = (value: string | undefined) => value?.toLowerCase();
112
-
113
- const screenshotMode = mode(this.parameters?.enableScreenshots || process.env.ENABLE_SCREENSHOTS);
114
- const videoMode = mode(this.parameters?.enableVideos || process.env.ENABLE_VIDEOS);
115
- const traceMode = mode(this.parameters?.enableTrace || process.env.ENABLE_TRACE);
116
-
117
- const shouldSaveScreenshot = screenshotMode === "all" || (screenshotMode === "fail" && failed);
118
- const shouldSaveVideo = videoMode === "all" || (videoMode === "fail" && failed);
119
- const shouldSaveTrace = traceMode === "all" || (traceMode === "fail" && failed);
120
-
121
- // ๐Ÿ“ธ Screenshot
122
- if (shouldSaveScreenshot && this.page) {
123
- const screenshotPath = path.join(
124
- this.data.screenshotDir,
125
- `${failed ? "failed-" : ""}${name}.png`
126
- );
127
- try {
128
- fs.mkdirSync(this.data.screenshotDir, { recursive: true });
129
- await this.page.screenshot({ path: screenshotPath, fullPage: true });
130
- console.log(`๐Ÿ–ผ๏ธ Screenshot saved: ${screenshotPath}`);
131
- } catch (err) {
132
- console.warn("โŒ Failed to save screenshot:", err);
133
- }
134
- }
135
-
136
- // ๐ŸŽฅ Video
137
- if (this.page && videoMode !== "false") {
138
- try {
139
- const video = this.page.video();
140
- if (video) {
141
- const rawPath = await video.path();
142
- if (fs.existsSync(rawPath)) {
143
- const finalPath = path.join(this.data.videoDir, `${failed ? "failed-" : ""}${name}.webm`);
144
- fs.mkdirSync(this.data.videoDir, { recursive: true });
145
- shouldSaveVideo ? fs.renameSync(rawPath, finalPath) : fs.unlinkSync(rawPath);
146
- console.log(`${shouldSaveVideo ? "๐ŸŽฅ Video saved" : "๐Ÿงน Deleted video"}: ${finalPath}`);
147
- }
148
- }
149
- } catch (err) {
150
- console.warn(`โš ๏ธ Video error: ${(err as Error).message}`);
151
- }
152
- }
153
-
154
- // ๐Ÿงช Tracing
155
- if (this.context && this.data.tracingStarted) {
156
- const tracePath = path.join(this.data.artifactDir, "traces", `${name}.zip`);
157
- try {
158
- fs.mkdirSync(path.dirname(tracePath), { recursive: true });
159
- await this.context.tracing.stop({ path: tracePath });
160
- shouldSaveTrace
161
- ? console.log(`๐Ÿ“ฆ Trace saved: ${tracePath}`)
162
- : (fs.existsSync(tracePath) && fs.unlinkSync(tracePath),
163
- console.log(`๐Ÿงน Trace discarded: ${tracePath}`));
164
- } catch (err) {
165
- console.warn("โŒ Trace handling error:", err);
166
- }
167
- }
168
-
169
- // ๐Ÿงช Visual regression
170
- if (this.page && this.data.enableVisualTest) {
171
- const BASELINE_DIR = path.resolve(this.data.artifactDir, "snapshots/baseline");
172
- const DIFF_DIR = path.resolve(this.data.artifactDir, "snapshots/diff");
173
-
174
- fs.mkdirSync(BASELINE_DIR, { recursive: true });
175
- fs.mkdirSync(DIFF_DIR, { recursive: true });
176
-
177
- const baselinePath = path.join(BASELINE_DIR, `${name}.png`);
178
- const actualPath = path.join(DIFF_DIR, `${name}.actual.png`);
179
- const diffPath = path.join(DIFF_DIR, `${name}.diff.png`);
180
-
181
- await this.page.screenshot({ path: actualPath, fullPage: true });
182
-
183
- if (!fs.existsSync(baselinePath)) {
184
- fs.copyFileSync(actualPath, baselinePath);
185
- console.log(`๐Ÿ“ธ Created baseline image: ${baselinePath}`);
186
- } else {
187
- try {
188
- const diffPixels = compareSnapshots({
189
- actualPath,
190
- baselinePath,
191
- diffPath,
192
- threshold: 0.1,
193
- });
194
-
195
- console.log(
196
- diffPixels > 0
197
- ? `โš ๏ธ Visual diff found (${diffPixels} pixels): ${diffPath}`
198
- : "โœ… No visual changes detected"
199
- );
200
- } catch (err) {
201
- console.warn("โŒ Snapshot comparison failed:", err);
202
- }
203
- }
204
- }
205
-
206
- // Cleanup
207
- try {
208
- await this.cleanup(scenario);
209
- } catch (err) {
210
- this.log?.("โŒ Error during cleanup: " + (err as Error).message);
211
- }
212
- });