e2e-testid 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.
- package/README.md +166 -0
- package/index.js +126 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
Here is a clean, professional `README.md` you can use for your package:
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# e2e-testid
|
|
6
|
+
|
|
7
|
+
A lightweight utility for generating stable and consistent `data-testid` values for E2E testing.
|
|
8
|
+
|
|
9
|
+
It helps teams standardize selectors, avoid flaky tests, and create namespaced test IDs in a clean and scalable way.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## ✨ Why e2e-testid?
|
|
14
|
+
|
|
15
|
+
E2E tests often break because:
|
|
16
|
+
|
|
17
|
+
* Selectors are inconsistent
|
|
18
|
+
* Developers rename classes
|
|
19
|
+
* IDs are not standardized
|
|
20
|
+
* There is no naming convention
|
|
21
|
+
|
|
22
|
+
`e2e-testid` solves this by providing:
|
|
23
|
+
|
|
24
|
+
* Consistent `data-testid` naming
|
|
25
|
+
* Namespacing support (`Auth.Login.Submit`)
|
|
26
|
+
* Automatic test id builder per feature
|
|
27
|
+
* Optional runtime toggle (enable only in E2E builds)
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 📦 Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install e2e-testid
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
or
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
yarn add e2e-testid
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 🚀 Basic Usage
|
|
46
|
+
|
|
47
|
+
```js
|
|
48
|
+
const { tid } = require("e2e-testid");
|
|
49
|
+
|
|
50
|
+
<button {...tid("Auth.Login.Submit")}>
|
|
51
|
+
Login
|
|
52
|
+
</button>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Output:
|
|
56
|
+
|
|
57
|
+
```html
|
|
58
|
+
<button data-testid="Auth.Login.Submit">
|
|
59
|
+
Login
|
|
60
|
+
</button>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 🏗 Namespaced Builder
|
|
66
|
+
|
|
67
|
+
Create a namespace for a feature:
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
const { createTid } = require("e2e-testid");
|
|
71
|
+
|
|
72
|
+
const t = createTid("Auth.Login");
|
|
73
|
+
|
|
74
|
+
<button {...t.tid("Submit")}>
|
|
75
|
+
Login
|
|
76
|
+
</button>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Result:
|
|
80
|
+
|
|
81
|
+
```html
|
|
82
|
+
<button data-testid="Auth.Login.Submit">
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 🔁 Child Namespaces
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
const auth = createTid("Auth");
|
|
91
|
+
const login = auth.child("Login");
|
|
92
|
+
|
|
93
|
+
login.tid("Submit");
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Generates:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
Auth.Login.Submit
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## ⚙️ Enable / Disable Test IDs
|
|
105
|
+
|
|
106
|
+
You can enable test IDs only in E2E environments:
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
const { configure } = require("e2e-testid");
|
|
110
|
+
|
|
111
|
+
configure({ enabled: true });
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Or automatically enable using environment variable:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
E2E=true
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
This prevents `data-testid` from being included in production builds if desired.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 🧠 Recommended Naming Convention
|
|
125
|
+
|
|
126
|
+
Use structured names:
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
Feature.Screen.Element
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Examples:
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
Auth.Login.EmailInput
|
|
136
|
+
Auth.Login.Submit
|
|
137
|
+
Cart.Checkout.Total
|
|
138
|
+
Checkout.Success.Message
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
This keeps your test suite readable and scalable.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## 🎯 Works With
|
|
146
|
+
|
|
147
|
+
* Playwright
|
|
148
|
+
* Cypress
|
|
149
|
+
* Selenium
|
|
150
|
+
* Any E2E framework
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## 📌 Example With Playwright
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
await page.getByTestId("Auth.Login.Submit").click();
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 📄 License
|
|
163
|
+
|
|
164
|
+
ISC
|
|
165
|
+
|
|
166
|
+
|
package/index.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
const state = {
|
|
2
|
+
enabled: (() => {
|
|
3
|
+
const v =
|
|
4
|
+
(typeof process !== "undefined" &&
|
|
5
|
+
process.env &&
|
|
6
|
+
(process.env.E2E_TESTIDS || process.env.E2E || process.env.NODE_ENV)) ||
|
|
7
|
+
"";
|
|
8
|
+
const s = String(v).toLowerCase();
|
|
9
|
+
if (s === "1" || s === "true" || s === "yes" || s === "on") return true;
|
|
10
|
+
if (s === "test" || s === "e2e") return true;
|
|
11
|
+
return false;
|
|
12
|
+
})(),
|
|
13
|
+
attr: "data-testid",
|
|
14
|
+
sep: ".",
|
|
15
|
+
sanitizer: (x) =>
|
|
16
|
+
String(x)
|
|
17
|
+
.trim()
|
|
18
|
+
.replace(/\s+/g, "_")
|
|
19
|
+
.replace(/[^\w.\-:/]/g, "_")
|
|
20
|
+
.replace(/_+/g, "_")
|
|
21
|
+
.replace(/^_+|_+$/g, "")
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function configure(options = {}) {
|
|
25
|
+
if (Object.prototype.hasOwnProperty.call(options, "enabled")) {
|
|
26
|
+
state.enabled = Boolean(options.enabled);
|
|
27
|
+
}
|
|
28
|
+
if (Object.prototype.hasOwnProperty.call(options, "attr") && options.attr) {
|
|
29
|
+
state.attr = String(options.attr);
|
|
30
|
+
}
|
|
31
|
+
if (Object.prototype.hasOwnProperty.call(options, "sep") && options.sep) {
|
|
32
|
+
state.sep = String(options.sep);
|
|
33
|
+
}
|
|
34
|
+
if (
|
|
35
|
+
Object.prototype.hasOwnProperty.call(options, "sanitizer") &&
|
|
36
|
+
typeof options.sanitizer === "function"
|
|
37
|
+
) {
|
|
38
|
+
state.sanitizer = options.sanitizer;
|
|
39
|
+
}
|
|
40
|
+
return getConfig();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getConfig() {
|
|
44
|
+
return { enabled: state.enabled, attr: state.attr, sep: state.sep };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isEnabled() {
|
|
48
|
+
return state.enabled;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function normalizePart(part) {
|
|
52
|
+
if (part === null || part === undefined) return "";
|
|
53
|
+
const s = state.sanitizer(part);
|
|
54
|
+
return s;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function joinParts(parts) {
|
|
58
|
+
const out = [];
|
|
59
|
+
for (const p of parts) {
|
|
60
|
+
const n = normalizePart(p);
|
|
61
|
+
if (n) out.push(n);
|
|
62
|
+
}
|
|
63
|
+
return out.join(state.sep);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function ensureId(idOrParts) {
|
|
67
|
+
if (Array.isArray(idOrParts)) return joinParts(idOrParts);
|
|
68
|
+
return normalizePart(idOrParts);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function tid(id, extraProps) {
|
|
72
|
+
const props = extraProps && typeof extraProps === "object" ? extraProps : {};
|
|
73
|
+
if (!state.enabled) return props;
|
|
74
|
+
const testId = ensureId(id);
|
|
75
|
+
if (!testId) return props;
|
|
76
|
+
return { ...props, [state.attr]: testId };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function tidValue(id) {
|
|
80
|
+
const testId = ensureId(id);
|
|
81
|
+
return testId;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function createTid(namespace) {
|
|
85
|
+
const base = normalizePart(namespace);
|
|
86
|
+
const builder = (...parts) => joinParts([base, ...parts]);
|
|
87
|
+
|
|
88
|
+
builder.base = () => base;
|
|
89
|
+
|
|
90
|
+
builder.of = (...parts) => joinParts([base, ...parts]);
|
|
91
|
+
|
|
92
|
+
builder.tid = (...parts) => tid(joinParts([base, ...parts]));
|
|
93
|
+
|
|
94
|
+
builder.value = (...parts) => tidValue(joinParts([base, ...parts]));
|
|
95
|
+
|
|
96
|
+
builder.with = (parts, extraProps) =>
|
|
97
|
+
tid(
|
|
98
|
+
joinParts([base, ...(Array.isArray(parts) ? parts : [parts])]),
|
|
99
|
+
extraProps
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
builder.child = (childNamespace) => createTid(joinParts([base, childNamespace]));
|
|
103
|
+
|
|
104
|
+
builder.enable = () => configure({ enabled: true });
|
|
105
|
+
|
|
106
|
+
builder.disable = () => configure({ enabled: false });
|
|
107
|
+
|
|
108
|
+
return builder;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function makeJourneyTid(feature, screen) {
|
|
112
|
+
const f = normalizePart(feature);
|
|
113
|
+
const s = normalizePart(screen);
|
|
114
|
+
const t = createTid(joinParts([f, s]));
|
|
115
|
+
return t;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = {
|
|
119
|
+
configure,
|
|
120
|
+
getConfig,
|
|
121
|
+
isEnabled,
|
|
122
|
+
tid,
|
|
123
|
+
tidValue,
|
|
124
|
+
createTid,
|
|
125
|
+
makeJourneyTid
|
|
126
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "e2e-testid",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "A small utility for stable and consistent data-testid generation for E2E testing.",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"author": "Taha Kourani",
|
|
7
|
+
"type": "commonjs",
|
|
8
|
+
"main": "index.js",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./index.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"e2e",
|
|
14
|
+
"testing",
|
|
15
|
+
"testid",
|
|
16
|
+
"data-testid",
|
|
17
|
+
"playwright",
|
|
18
|
+
"cypress",
|
|
19
|
+
"selectors",
|
|
20
|
+
"react"
|
|
21
|
+
],
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/TechTaha-lab/react-packages.git"
|
|
25
|
+
},
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/TechTaha-lab/react-packages/issues"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/TechTaha-lab/react-packages#readme",
|
|
30
|
+
"files": [
|
|
31
|
+
"index.js",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
35
|
+
"sideEffects": false,
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=14"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"test": "echo \"No tests yet\" && exit 0"
|
|
44
|
+
}
|
|
45
|
+
}
|