playwright-cucumber-ts-steps 0.1.0 โ 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/clickSteps.js +35 -33
- package/dist/actions/cookieSteps.js +7 -5
- package/dist/actions/debugSteps.js +5 -3
- package/dist/actions/elementFindSteps.js +50 -48
- package/dist/actions/inputSteps.js +40 -35
- package/dist/actions/interceptionSteps.js +9 -7
- package/dist/actions/miscSteps.js +41 -36
- package/dist/actions/mouseSteps.js +10 -8
- package/dist/actions/scrollSteps.js +7 -5
- package/dist/actions/storageSteps.js +10 -8
- package/dist/assertions/buttonAndTextVisibilitySteps.js +25 -23
- package/dist/assertions/cookieSteps.js +7 -5
- package/dist/assertions/elementSteps.js +24 -22
- package/dist/assertions/formInputSteps.js +28 -26
- package/dist/assertions/interceptionRequestsSteps.js +27 -25
- package/dist/assertions/locationSteps.js +17 -15
- package/dist/assertions/roleTestIdSteps.js +12 -10
- package/dist/assertions/semanticSteps.js +9 -7
- package/dist/assertions/storageSteps.js +23 -18
- package/dist/assertions/visualSteps.js +41 -36
- package/dist/custom_setups/globalLogin.js +10 -5
- package/dist/custom_setups/loginHooks.js +30 -25
- package/dist/helpers/compareSnapshots.js +15 -9
- package/dist/helpers/hooks.js +73 -35
- package/dist/helpers/utils/fakerUtils.js +32 -26
- package/dist/helpers/utils/index.js +19 -3
- package/dist/helpers/utils/optionsUtils.js +18 -8
- package/dist/helpers/utils/resolveUtils.js +19 -11
- package/dist/helpers/world.js +45 -8
- package/dist/iframes/frames.js +4 -2
- package/dist/index.js +43 -27
- package/dist/register.js +3 -1
- package/package.json +2 -2
|
@@ -1,26 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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 cucumber_1 = require("@cucumber/cucumber");
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
4
9
|
//
|
|
5
10
|
// ๐ LOCAL STORAGE
|
|
6
11
|
//
|
|
7
|
-
Then("I see local storage item {string}", async function (key) {
|
|
12
|
+
(0, cucumber_1.Then)("I see local storage item {string}", async function (key) {
|
|
8
13
|
const value = await this.page.evaluate((k) => localStorage.getItem(k), key);
|
|
9
14
|
if (value === null)
|
|
10
15
|
throw new Error(`Local storage item "${key}" not found`);
|
|
11
16
|
});
|
|
12
|
-
Then("I do not see local storage item {string}", async function (key) {
|
|
17
|
+
(0, cucumber_1.Then)("I do not see local storage item {string}", async function (key) {
|
|
13
18
|
const value = await this.page.evaluate((k) => localStorage.getItem(k), key);
|
|
14
19
|
if (value !== null)
|
|
15
20
|
throw new Error(`Expected localStorage["${key}"] to be null, but got "${value}"`);
|
|
16
21
|
});
|
|
17
|
-
Then("I see local storage item {string} equals {string}", async function (key, expected) {
|
|
22
|
+
(0, cucumber_1.Then)("I see local storage item {string} equals {string}", async function (key, expected) {
|
|
18
23
|
const actual = await this.page.evaluate((k) => localStorage.getItem(k), key);
|
|
19
24
|
if (actual !== expected) {
|
|
20
25
|
throw new Error(`Expected localStorage["${key}"] to be "${expected}", but got "${actual}"`);
|
|
21
26
|
}
|
|
22
27
|
});
|
|
23
|
-
Then("I see local storage item {string} contains {string}", async function (key, part) {
|
|
28
|
+
(0, cucumber_1.Then)("I see local storage item {string} contains {string}", async function (key, part) {
|
|
24
29
|
const value = await this.page.evaluate((k) => localStorage.getItem(k), key);
|
|
25
30
|
if (!value || !value.includes(part)) {
|
|
26
31
|
throw new Error(`localStorage["${key}"] does not contain "${part}". Got: "${value}"`);
|
|
@@ -29,24 +34,24 @@ Then("I see local storage item {string} contains {string}", async function (key,
|
|
|
29
34
|
//
|
|
30
35
|
// ๐ SESSION STORAGE
|
|
31
36
|
//
|
|
32
|
-
Then("I see session storage item {string}", async function (key) {
|
|
37
|
+
(0, cucumber_1.Then)("I see session storage item {string}", async function (key) {
|
|
33
38
|
const value = await this.page.evaluate((k) => sessionStorage.getItem(k), key);
|
|
34
39
|
if (value === null)
|
|
35
40
|
throw new Error(`Session storage item "${key}" not found`);
|
|
36
41
|
});
|
|
37
|
-
Then("I do not see session storage item {string}", async function (key) {
|
|
42
|
+
(0, cucumber_1.Then)("I do not see session storage item {string}", async function (key) {
|
|
38
43
|
const value = await this.page.evaluate((k) => sessionStorage.getItem(k), key);
|
|
39
44
|
if (value !== null)
|
|
40
45
|
throw new Error(`Expected sessionStorage["${key}"] to be null, but got "${value}"`);
|
|
41
46
|
});
|
|
42
|
-
When("I clear all saved session files", async function () {
|
|
43
|
-
const authDir =
|
|
44
|
-
if (
|
|
45
|
-
const files =
|
|
47
|
+
(0, cucumber_1.When)("I clear all saved session files", async function () {
|
|
48
|
+
const authDir = path_1.default.resolve("e2e/support/helper/auth");
|
|
49
|
+
if (fs_1.default.existsSync(authDir)) {
|
|
50
|
+
const files = fs_1.default.readdirSync(authDir);
|
|
46
51
|
for (const file of files) {
|
|
47
|
-
const filePath =
|
|
48
|
-
if (
|
|
49
|
-
|
|
52
|
+
const filePath = path_1.default.join(authDir, file);
|
|
53
|
+
if (fs_1.default.lstatSync(filePath).isFile()) {
|
|
54
|
+
fs_1.default.unlinkSync(filePath);
|
|
50
55
|
this.log?.(`๐งน Deleted session file: ${file}`);
|
|
51
56
|
}
|
|
52
57
|
}
|
|
@@ -55,13 +60,13 @@ When("I clear all saved session files", async function () {
|
|
|
55
60
|
this.log?.(`โ ๏ธ Auth directory not found at ${authDir}`);
|
|
56
61
|
}
|
|
57
62
|
});
|
|
58
|
-
Then("I see session storage item {string} equals {string}", async function (key, expected) {
|
|
63
|
+
(0, cucumber_1.Then)("I see session storage item {string} equals {string}", async function (key, expected) {
|
|
59
64
|
const actual = await this.page.evaluate((k) => sessionStorage.getItem(k), key);
|
|
60
65
|
if (actual !== expected) {
|
|
61
66
|
throw new Error(`Expected sessionStorage["${key}"] to be "${expected}", but got "${actual}"`);
|
|
62
67
|
}
|
|
63
68
|
});
|
|
64
|
-
Then("I see session storage item {string} contains {string}", async function (key, part) {
|
|
69
|
+
(0, cucumber_1.Then)("I see session storage item {string} contains {string}", async function (key, part) {
|
|
65
70
|
const value = await this.page.evaluate((k) => sessionStorage.getItem(k), key);
|
|
66
71
|
if (!value || !value.includes(part)) {
|
|
67
72
|
throw new Error(`sessionStorage["${key}"] does not contain "${part}". Got: "${value}"`);
|
|
@@ -1,67 +1,72 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
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 cucumber_1 = require("@cucumber/cucumber");
|
|
7
|
+
const pixelmatch_1 = __importDefault(require("pixelmatch"));
|
|
8
|
+
const pngjs_1 = require("pngjs");
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const test_1 = require("@playwright/test");
|
|
12
|
+
const BASELINE_DIR = path_1.default.resolve("e2e/snapshots/baseline");
|
|
13
|
+
const CURRENT_DIR = path_1.default.resolve("e2e/snapshots/current");
|
|
14
|
+
const DIFF_DIR = path_1.default.resolve("e2e/snapshots/diff");
|
|
10
15
|
function getSnapshotPaths(name) {
|
|
11
16
|
const safeName = name.replace(/[^a-z0-9]/gi, "_").toLowerCase();
|
|
12
17
|
return {
|
|
13
|
-
baseline:
|
|
14
|
-
current:
|
|
15
|
-
diff:
|
|
18
|
+
baseline: path_1.default.join(BASELINE_DIR, `${safeName}.png`),
|
|
19
|
+
current: path_1.default.join(CURRENT_DIR, `${safeName}.png`),
|
|
20
|
+
diff: path_1.default.join(DIFF_DIR, `${safeName}.diff.png`),
|
|
16
21
|
};
|
|
17
22
|
}
|
|
18
|
-
Then("I should see the page matches the snapshot {string}", async function (name) {
|
|
23
|
+
(0, cucumber_1.Then)("I should see the page matches the snapshot {string}", async function (name) {
|
|
19
24
|
const { page } = this;
|
|
20
25
|
const paths = getSnapshotPaths(name);
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
fs_1.default.mkdirSync(BASELINE_DIR, { recursive: true });
|
|
27
|
+
fs_1.default.mkdirSync(CURRENT_DIR, { recursive: true });
|
|
28
|
+
fs_1.default.mkdirSync(DIFF_DIR, { recursive: true });
|
|
24
29
|
await page.screenshot({ path: paths.current, fullPage: true });
|
|
25
|
-
if (!
|
|
26
|
-
|
|
30
|
+
if (!fs_1.default.existsSync(paths.baseline)) {
|
|
31
|
+
fs_1.default.copyFileSync(paths.current, paths.baseline);
|
|
27
32
|
this.log?.(`๐ธ Created baseline snapshot: ${paths.baseline}`);
|
|
28
33
|
return;
|
|
29
34
|
}
|
|
30
|
-
const baseline = PNG.sync.read(
|
|
31
|
-
const current = PNG.sync.read(
|
|
35
|
+
const baseline = pngjs_1.PNG.sync.read(fs_1.default.readFileSync(paths.baseline));
|
|
36
|
+
const current = pngjs_1.PNG.sync.read(fs_1.default.readFileSync(paths.current));
|
|
32
37
|
const { width, height } = baseline;
|
|
33
|
-
const diff = new PNG({ width, height });
|
|
34
|
-
const pixelDiff =
|
|
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 });
|
|
35
40
|
if (pixelDiff > 0) {
|
|
36
|
-
|
|
41
|
+
fs_1.default.writeFileSync(paths.diff, pngjs_1.PNG.sync.write(diff));
|
|
37
42
|
this.log?.(`โ Visual mismatch detected, diff: ${paths.diff}`);
|
|
38
43
|
}
|
|
39
|
-
expect(pixelDiff, "Pixels that differ").toBe(0);
|
|
44
|
+
(0, test_1.expect)(pixelDiff, "Pixels that differ").toBe(0);
|
|
40
45
|
});
|
|
41
|
-
Then("I capture a snapshot of the element {string} as {string}", async function (selector, alias) {
|
|
46
|
+
(0, cucumber_1.Then)("I capture a snapshot of the element {string} as {string}", async function (selector, alias) {
|
|
42
47
|
const element = this.getScope().locator(selector);
|
|
43
|
-
const pathCurrent =
|
|
44
|
-
|
|
48
|
+
const pathCurrent = path_1.default.join(CURRENT_DIR, `${alias}.png`);
|
|
49
|
+
fs_1.default.mkdirSync(CURRENT_DIR, { recursive: true });
|
|
45
50
|
await element.screenshot({ path: pathCurrent });
|
|
46
51
|
this.log?.(`๐ธ Snapshot for ${selector} saved as ${alias}`);
|
|
47
52
|
});
|
|
48
|
-
Then("The snapshot {string} should match baseline", async function (alias) {
|
|
53
|
+
(0, cucumber_1.Then)("The snapshot {string} should match baseline", async function (alias) {
|
|
49
54
|
const paths = getSnapshotPaths(alias);
|
|
50
|
-
const current = PNG.sync.read(
|
|
51
|
-
const baseline =
|
|
52
|
-
? PNG.sync.read(
|
|
55
|
+
const current = pngjs_1.PNG.sync.read(fs_1.default.readFileSync(paths.current));
|
|
56
|
+
const baseline = fs_1.default.existsSync(paths.baseline)
|
|
57
|
+
? pngjs_1.PNG.sync.read(fs_1.default.readFileSync(paths.baseline))
|
|
53
58
|
: null;
|
|
54
59
|
if (!baseline) {
|
|
55
|
-
|
|
60
|
+
fs_1.default.copyFileSync(paths.current, paths.baseline);
|
|
56
61
|
this.log?.(`๐ธ Created new baseline for ${alias}`);
|
|
57
62
|
return;
|
|
58
63
|
}
|
|
59
64
|
const { width, height } = baseline;
|
|
60
|
-
const diff = new PNG({ width, height });
|
|
61
|
-
const pixelDiff =
|
|
65
|
+
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 });
|
|
62
67
|
if (pixelDiff > 0) {
|
|
63
|
-
|
|
68
|
+
fs_1.default.writeFileSync(paths.diff, pngjs_1.PNG.sync.write(diff));
|
|
64
69
|
this.log?.(`โ ๏ธ Snapshot mismatch: ${alias}`);
|
|
65
70
|
}
|
|
66
|
-
expect(pixelDiff).toBe(0);
|
|
71
|
+
(0, test_1.expect)(pixelDiff).toBe(0);
|
|
67
72
|
});
|
|
@@ -1,8 +1,13 @@
|
|
|
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 });
|
|
1
6
|
// global-setup.ts
|
|
2
|
-
|
|
3
|
-
|
|
7
|
+
const test_1 = require("@playwright/test");
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
4
9
|
async function globalSetup() {
|
|
5
|
-
const browser = await chromium.launch();
|
|
10
|
+
const browser = await test_1.chromium.launch();
|
|
6
11
|
const page = await browser.newPage();
|
|
7
12
|
// Navigate to login and log in
|
|
8
13
|
await page.goto(process.env.PLAYWRIGHT_BASE_URL || "http://demoqa.com");
|
|
@@ -14,7 +19,7 @@ async function globalSetup() {
|
|
|
14
19
|
// Save session to file
|
|
15
20
|
await page
|
|
16
21
|
.context()
|
|
17
|
-
.storageState({ path:
|
|
22
|
+
.storageState({ path: path_1.default.resolve(__dirname, "storageState.json") });
|
|
18
23
|
await browser.close();
|
|
19
24
|
}
|
|
20
|
-
|
|
25
|
+
exports.default = globalSetup;
|
|
@@ -1,13 +1,18 @@
|
|
|
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 });
|
|
1
6
|
// e2e/step_definitions/auth/loginHooks.ts
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
const cucumber_1 = require("@cucumber/cucumber");
|
|
8
|
+
const test_1 = require("@playwright/test");
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const resolveUtils_1 = require("../helpers/utils/resolveUtils");
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const console_1 = require("console");
|
|
8
13
|
async function tryRestoreSession(world, sessionName) {
|
|
9
|
-
const storagePath =
|
|
10
|
-
if (
|
|
14
|
+
const storagePath = path_1.default.resolve("e2e/support/helper/auth", `${sessionName}.json`);
|
|
15
|
+
if (fs_1.default.existsSync(storagePath)) {
|
|
11
16
|
await world.context?.addCookies([]);
|
|
12
17
|
await world.page.context().addInitScript(() => {
|
|
13
18
|
// preload logic if needed
|
|
@@ -23,30 +28,30 @@ async function loginAndSaveSession(world, email, password, sessionName) {
|
|
|
23
28
|
if (!baseUrl)
|
|
24
29
|
throw new Error("Missing BASE_URL in environment");
|
|
25
30
|
await pageLogin(world, baseUrl, email, password);
|
|
26
|
-
const sessionFile =
|
|
31
|
+
const sessionFile = path_1.default.resolve("e2e/support/helper/auth", `${sessionName}.json`);
|
|
27
32
|
await world.page.context().storageState({ path: sessionFile });
|
|
28
33
|
world.log(`Session saved to ${sessionFile}`);
|
|
29
34
|
}
|
|
30
35
|
const loginStep = async function (user, pass) {
|
|
31
|
-
const email = resolveValue(user);
|
|
32
|
-
const password = resolveValue(pass);
|
|
33
|
-
const sessionName = deriveSessionName(email);
|
|
36
|
+
const email = (0, resolveUtils_1.resolveValue)(user);
|
|
37
|
+
const password = (0, resolveUtils_1.resolveValue)(pass);
|
|
38
|
+
const sessionName = (0, resolveUtils_1.deriveSessionName)(email);
|
|
34
39
|
const restored = await tryRestoreSession(this, sessionName);
|
|
35
40
|
if (!restored) {
|
|
36
41
|
await loginAndSaveSession(this, email, password, sessionName);
|
|
37
42
|
}
|
|
38
43
|
};
|
|
39
|
-
When("I am logged out", async function () {
|
|
44
|
+
(0, cucumber_1.When)("I am logged out", async function () {
|
|
40
45
|
const { page } = this;
|
|
41
46
|
await page.goto("/");
|
|
42
47
|
const url = page.url();
|
|
43
48
|
if (!url.includes("/login")) {
|
|
44
49
|
await page.locator("//div[@id=':rti:']//*[name()='svg']").last().click();
|
|
45
50
|
await page.getByText("Logout").click();
|
|
46
|
-
await expect(page.getByText("Log in")).toBeVisible();
|
|
51
|
+
await (0, test_1.expect)(page.getByText("Log in")).toBeVisible();
|
|
47
52
|
}
|
|
48
53
|
});
|
|
49
|
-
When("I login as a default user", async function () {
|
|
54
|
+
(0, cucumber_1.When)("I login as a default user", async function () {
|
|
50
55
|
const baseUrl = process.env.BASE_URL;
|
|
51
56
|
const email = process.env.USER_EMAIL;
|
|
52
57
|
const password = process.env.USER_PASSWORD;
|
|
@@ -56,18 +61,18 @@ When("I login as a default user", async function () {
|
|
|
56
61
|
}
|
|
57
62
|
await pageLogin(this, baseUrl, email, password);
|
|
58
63
|
});
|
|
59
|
-
When("I login with user {string} and password {string}", loginStep);
|
|
64
|
+
(0, cucumber_1.When)("I login with user {string} and password {string}", loginStep);
|
|
60
65
|
// Given("I login with user {string} and password {string}", loginStep);
|
|
61
66
|
// Special alias step
|
|
62
|
-
When("I login as {string} user", async function (alias) {
|
|
63
|
-
const sessionFile =
|
|
67
|
+
(0, cucumber_1.When)("I login as {string} user", async function (alias) {
|
|
68
|
+
const sessionFile = path_1.default.resolve("storage", `${alias}User.json`);
|
|
64
69
|
const email = process.env.USER_EMAIL;
|
|
65
70
|
const password = process.env.USER_PASSWORD;
|
|
66
71
|
const baseUrl = process.env.BASE_URL;
|
|
67
72
|
if (!email || !password) {
|
|
68
73
|
throw new Error("USER_EMAIL or USER_PASSWORD not set in .env");
|
|
69
74
|
}
|
|
70
|
-
if (
|
|
75
|
+
if (fs_1.default.existsSync(sessionFile)) {
|
|
71
76
|
await this.context.addCookies([]); // optional reset
|
|
72
77
|
await this.context.addInitScript(() => { });
|
|
73
78
|
await this.context.storageState({ path: sessionFile });
|
|
@@ -89,10 +94,10 @@ When("I login as {string} user", async function (alias) {
|
|
|
89
94
|
await this.context.storageState({ path: sessionFile });
|
|
90
95
|
this.log(`๐พ Saved session to ${alias}User.json`);
|
|
91
96
|
});
|
|
92
|
-
When("I perform login with:", async function (dataTable) {
|
|
97
|
+
(0, cucumber_1.When)("I perform login with:", async function (dataTable) {
|
|
93
98
|
const loginData = Object.fromEntries(dataTable.raw());
|
|
94
|
-
const email = resolveLoginValue(loginData.email, this);
|
|
95
|
-
const password = resolveLoginValue(loginData.password, this) ?? process.env.USER_PASSWORD;
|
|
99
|
+
const email = (0, resolveUtils_1.resolveLoginValue)(loginData.email, this);
|
|
100
|
+
const password = (0, resolveUtils_1.resolveLoginValue)(loginData.password, this) ?? process.env.USER_PASSWORD;
|
|
96
101
|
if (!email)
|
|
97
102
|
throw new Error("Missing or invalid email for login");
|
|
98
103
|
if (!password)
|
|
@@ -117,15 +122,15 @@ async function pageLogin(world, baseUrl, email, password) {
|
|
|
117
122
|
await page.getByRole("button", { name: "Login" }).click();
|
|
118
123
|
await page.waitForLoadState("networkidle");
|
|
119
124
|
if (await page.getByText("Select an account").isVisible()) {
|
|
120
|
-
log("Login successful, navigating to accounts page");
|
|
125
|
+
(0, console_1.log)("Login successful, navigating to accounts page");
|
|
121
126
|
await page.goto(`${baseUrl}/indicina`);
|
|
122
127
|
}
|
|
123
|
-
await expect(page.getByText("Total unique customers")).toBeVisible({
|
|
128
|
+
await (0, test_1.expect)(page.getByText("Total unique customers")).toBeVisible({
|
|
124
129
|
timeout: 10000,
|
|
125
130
|
});
|
|
126
131
|
// โ
Save session after successful login
|
|
127
132
|
await page.context().storageState({
|
|
128
|
-
path:
|
|
133
|
+
path: path_1.default.resolve("e2e/support/helper/auth", "session.json"),
|
|
129
134
|
});
|
|
130
135
|
world.data["loggedIn"] = true;
|
|
131
136
|
world.log?.("โ
Logged in and session saved.");
|
|
@@ -1,14 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.compareSnapshots = compareSnapshots;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const pixelmatch_1 = __importDefault(require("pixelmatch"));
|
|
9
|
+
const pngjs_1 = require("pngjs");
|
|
10
|
+
function compareSnapshots({ actualPath, baselinePath, diffPath, threshold = 0.1, }) {
|
|
11
|
+
const actual = pngjs_1.PNG.sync.read(fs_1.default.readFileSync(actualPath));
|
|
12
|
+
const baseline = pngjs_1.PNG.sync.read(fs_1.default.readFileSync(baselinePath));
|
|
7
13
|
if (actual.width !== baseline.width || actual.height !== baseline.height) {
|
|
8
14
|
throw new Error("Snapshot size mismatch");
|
|
9
15
|
}
|
|
10
|
-
const diff = new PNG({ width: actual.width, height: actual.height });
|
|
11
|
-
const numDiffPixels =
|
|
12
|
-
|
|
16
|
+
const diff = new pngjs_1.PNG({ width: actual.width, height: actual.height });
|
|
17
|
+
const numDiffPixels = (0, pixelmatch_1.default)(actual.data, baseline.data, diff.data, actual.width, actual.height, { threshold });
|
|
18
|
+
fs_1.default.writeFileSync(diffPath, pngjs_1.PNG.sync.write(diff));
|
|
13
19
|
return numDiffPixels;
|
|
14
20
|
}
|
package/dist/helpers/hooks.js
CHANGED
|
@@ -1,18 +1,56 @@
|
|
|
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 });
|
|
1
39
|
// support/hooks.ts
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
40
|
+
const cucumber_1 = require("@cucumber/cucumber");
|
|
41
|
+
const playwright_1 = require("playwright");
|
|
42
|
+
const dotenv = __importStar(require("dotenv"));
|
|
43
|
+
const fs_1 = __importDefault(require("fs"));
|
|
44
|
+
const path_1 = __importDefault(require("path"));
|
|
45
|
+
const compareSnapshots_1 = require("./compareSnapshots");
|
|
8
46
|
dotenv.config();
|
|
9
|
-
const SESSION_FILE =
|
|
10
|
-
const SCREENSHOT_DIR =
|
|
11
|
-
const VIDEO_DIR =
|
|
12
|
-
const SNAPSHOT_BASELINE_DIR =
|
|
13
|
-
const SNAPSHOT_DIFF_DIR =
|
|
47
|
+
const SESSION_FILE = path_1.default.resolve("e2e/support/helper/auth", "session.json");
|
|
48
|
+
const SCREENSHOT_DIR = path_1.default.resolve("e2e/screenshots");
|
|
49
|
+
const VIDEO_DIR = path_1.default.resolve("e2e/videos");
|
|
50
|
+
const SNAPSHOT_BASELINE_DIR = path_1.default.resolve("e2e/snapshots/baseline");
|
|
51
|
+
const SNAPSHOT_DIFF_DIR = path_1.default.resolve("e2e/snapshots/diff");
|
|
14
52
|
let sharedBrowser;
|
|
15
|
-
BeforeAll(async () => {
|
|
53
|
+
(0, cucumber_1.BeforeAll)(async () => {
|
|
16
54
|
const dirsToClean = [
|
|
17
55
|
VIDEO_DIR,
|
|
18
56
|
SCREENSHOT_DIR,
|
|
@@ -20,8 +58,8 @@ BeforeAll(async () => {
|
|
|
20
58
|
];
|
|
21
59
|
for (const dir of dirsToClean) {
|
|
22
60
|
try {
|
|
23
|
-
if (
|
|
24
|
-
|
|
61
|
+
if (fs_1.default.existsSync(dir)) {
|
|
62
|
+
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
25
63
|
console.log(`๐งน Cleaned directory: ${dir}`);
|
|
26
64
|
}
|
|
27
65
|
}
|
|
@@ -29,20 +67,20 @@ BeforeAll(async () => {
|
|
|
29
67
|
console.warn(`โ ๏ธ Failed to clean directory ${dir}:`, err);
|
|
30
68
|
}
|
|
31
69
|
}
|
|
32
|
-
sharedBrowser = await chromium.launch({
|
|
70
|
+
sharedBrowser = await playwright_1.chromium.launch({
|
|
33
71
|
headless: process.env.HEADLESS !== "false",
|
|
34
72
|
});
|
|
35
73
|
console.log("๐ Launched shared browser for all scenarios");
|
|
36
74
|
});
|
|
37
|
-
AfterAll(async () => {
|
|
75
|
+
(0, cucumber_1.AfterAll)(async () => {
|
|
38
76
|
await sharedBrowser?.close();
|
|
39
77
|
console.log("๐งน Closed shared browser after all scenarios");
|
|
40
78
|
});
|
|
41
|
-
Before(async function (scenario) {
|
|
79
|
+
(0, cucumber_1.Before)(async function (scenario) {
|
|
42
80
|
// ๐ก๏ธ Ensure browser is still usable
|
|
43
81
|
if (!sharedBrowser || !sharedBrowser.isConnected()) {
|
|
44
82
|
console.warn("โ ๏ธ Shared browser was disconnected. Restarting...");
|
|
45
|
-
sharedBrowser = await chromium.launch({
|
|
83
|
+
sharedBrowser = await playwright_1.chromium.launch({
|
|
46
84
|
headless: process.env.HEADLESS !== "false",
|
|
47
85
|
});
|
|
48
86
|
}
|
|
@@ -52,9 +90,9 @@ Before(async function (scenario) {
|
|
|
52
90
|
process.env.VISUAL_TEST = "true";
|
|
53
91
|
const contextOptions = {
|
|
54
92
|
recordVideo: { dir: VIDEO_DIR },
|
|
55
|
-
...(isMobile ? devices["iPhone 13 Pro"] : {}),
|
|
93
|
+
...(isMobile ? playwright_1.devices["iPhone 13 Pro"] : {}),
|
|
56
94
|
};
|
|
57
|
-
if (
|
|
95
|
+
if (fs_1.default.existsSync(SESSION_FILE)) {
|
|
58
96
|
contextOptions.storageState = SESSION_FILE;
|
|
59
97
|
this.log?.("โ
Reusing session from saved file.");
|
|
60
98
|
}
|
|
@@ -66,14 +104,14 @@ Before(async function (scenario) {
|
|
|
66
104
|
if (isMobile)
|
|
67
105
|
this.log?.("๐ฑ Mobile emulation enabled (iPhone 13 Pro)");
|
|
68
106
|
});
|
|
69
|
-
After(async function (scenario) {
|
|
107
|
+
(0, cucumber_1.After)(async function (scenario) {
|
|
70
108
|
const failed = scenario.result?.status === "FAILED";
|
|
71
109
|
const name = scenario.pickle.name.replace(/[^a-z0-9]+/gi, "_").toLowerCase();
|
|
72
110
|
// ๐ธ Screenshot on failure
|
|
73
111
|
if (failed && this.page) {
|
|
74
|
-
const screenshotPath =
|
|
112
|
+
const screenshotPath = path_1.default.join(SCREENSHOT_DIR, `failed-${name}.png`);
|
|
75
113
|
try {
|
|
76
|
-
|
|
114
|
+
fs_1.default.mkdirSync(SCREENSHOT_DIR, { recursive: true });
|
|
77
115
|
await this.page.screenshot({ path: screenshotPath, fullPage: true });
|
|
78
116
|
console.log(`๐ผ๏ธ Screenshot saved: ${screenshotPath}`);
|
|
79
117
|
}
|
|
@@ -91,14 +129,14 @@ After(async function (scenario) {
|
|
|
91
129
|
}
|
|
92
130
|
if (rawPath) {
|
|
93
131
|
try {
|
|
94
|
-
const finalPath =
|
|
95
|
-
|
|
132
|
+
const finalPath = path_1.default.join(VIDEO_DIR, `failed-${name}.webm`);
|
|
133
|
+
fs_1.default.mkdirSync(VIDEO_DIR, { recursive: true });
|
|
96
134
|
if (failed) {
|
|
97
|
-
|
|
135
|
+
fs_1.default.renameSync(rawPath, finalPath);
|
|
98
136
|
console.log(`๐ฅ Video saved: ${finalPath}`);
|
|
99
137
|
}
|
|
100
138
|
else {
|
|
101
|
-
|
|
139
|
+
fs_1.default.unlinkSync(rawPath);
|
|
102
140
|
console.log(`๐งน Deleted video for passed test: ${rawPath}`);
|
|
103
141
|
}
|
|
104
142
|
}
|
|
@@ -108,19 +146,19 @@ After(async function (scenario) {
|
|
|
108
146
|
}
|
|
109
147
|
// ๐งช Visual regression testing
|
|
110
148
|
if (this.page && process.env.VISUAL_TEST === "true") {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const baselinePath =
|
|
114
|
-
const actualPath =
|
|
115
|
-
const diffPath =
|
|
149
|
+
fs_1.default.mkdirSync(SNAPSHOT_BASELINE_DIR, { recursive: true });
|
|
150
|
+
fs_1.default.mkdirSync(SNAPSHOT_DIFF_DIR, { recursive: true });
|
|
151
|
+
const baselinePath = path_1.default.join(SNAPSHOT_BASELINE_DIR, `${name}.png`);
|
|
152
|
+
const actualPath = path_1.default.join(SNAPSHOT_DIFF_DIR, `${name}.actual.png`);
|
|
153
|
+
const diffPath = path_1.default.join(SNAPSHOT_DIFF_DIR, `${name}.diff.png`);
|
|
116
154
|
await this.page.screenshot({ path: actualPath, fullPage: true });
|
|
117
|
-
if (!
|
|
118
|
-
|
|
155
|
+
if (!fs_1.default.existsSync(baselinePath)) {
|
|
156
|
+
fs_1.default.copyFileSync(actualPath, baselinePath);
|
|
119
157
|
console.log(`๐ธ Created baseline image: ${baselinePath}`);
|
|
120
158
|
}
|
|
121
159
|
else {
|
|
122
160
|
try {
|
|
123
|
-
const diffPixels = compareSnapshots({
|
|
161
|
+
const diffPixels = (0, compareSnapshots_1.compareSnapshots)({
|
|
124
162
|
actualPath,
|
|
125
163
|
baselinePath,
|
|
126
164
|
diffPath,
|