@syngrisi/wdio-sdk 2.6.1 → 3.0.0

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 CHANGED
@@ -256,6 +256,7 @@ macOS/Linux: `export SYNGRISI_LOG_LEVEL=debug`
256
256
  `ENV_POSTFIX` - will add to platform property, you can use this to set some unique platform name for particular
257
257
  environment
258
258
  `SYNGRISI_LOG_LEVEL` - logging level (`"trace" | "debug" | "info" | "warn" | "error"`)
259
+ `SYNGRISI_DISABLE_DOM_DATA` - disable DOM data collection for RCA (`"true" | "false"`), default: `"true"`
259
260
 
260
261
  ## Documentation
261
262
 
@@ -209,6 +209,9 @@ class WDIODriver {
209
209
  if (!Buffer.isBuffer(imageBuffer))
210
210
  throw new Error('check - wrong imageBuffer');
211
211
  let opts = null;
212
+ // Check if DOM data should be skipped (via env var or option)
213
+ // Default: skipDomData is true (disabled) unless explicitly enabled via env var or params
214
+ const skipDomData = params?.skipDomData ?? (process.env.SYNGRISI_DISABLE_DOM_DATA !== 'false');
212
215
  try {
213
216
  opts = {
214
217
  // ident: ['name', 'viewport', 'browserName', 'os', 'app', 'branch'];
@@ -224,7 +227,8 @@ class WDIODriver {
224
227
  browserVersion: this.params.test.browserVersion,
225
228
  browserFullVersion: this.params.test.browserFullVersion,
226
229
  hashCode: (0, node_crypto_1.createHash)('sha512').update(imageBuffer).digest('hex'),
227
- domDump: domDump,
230
+ domDump: skipDomData ? undefined : domDump,
231
+ skipDomData: skipDomData || undefined,
228
232
  };
229
233
  (0, paramsGuard_1.paramsGuard)(opts, 'check, opts', core_api_1.CheckParamsSchema);
230
234
  // Remove autoAccept from params before sending to API (it's SDK-only)
@@ -1,78 +1,156 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getDomDump = getDomDump;
4
+ /**
5
+ * Maximum depth for DOM tree traversal to prevent stack overflow
6
+ */
7
+ const MAX_DEPTH = 15;
8
+ /**
9
+ * Maximum text length to capture for each element
10
+ */
11
+ const MAX_TEXT_LENGTH = 200;
12
+ /**
13
+ * Tags to skip during DOM collection (non-visual elements)
14
+ */
15
+ const SKIP_TAGS = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'META', 'LINK', 'HEAD', 'SVG', 'PATH'];
16
+ /**
17
+ * CSS properties to capture for RCA analysis
18
+ */
19
+ const STYLES_TO_CAPTURE = [
20
+ // Display & Visibility
21
+ 'display', 'visibility', 'opacity',
22
+ // Position
23
+ 'position', 'top', 'right', 'bottom', 'left',
24
+ // Dimensions
25
+ 'width', 'height', 'min-width', 'min-height', 'max-width', 'max-height',
26
+ // Box Model
27
+ 'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
28
+ 'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
29
+ 'border', 'border-width', 'border-style', 'border-color',
30
+ 'border-radius',
31
+ // Colors
32
+ 'background-color', 'color',
33
+ // Typography
34
+ 'font-family', 'font-size', 'font-weight', 'line-height', 'text-align',
35
+ 'text-decoration', 'text-transform', 'letter-spacing',
36
+ // Layout
37
+ 'overflow', 'overflow-x', 'overflow-y',
38
+ 'z-index',
39
+ 'transform',
40
+ // Flexbox
41
+ 'flex', 'flex-direction', 'flex-wrap', 'justify-content', 'align-items', 'align-content', 'gap',
42
+ // Grid
43
+ 'grid-template-columns', 'grid-template-rows', 'grid-gap',
44
+ // Box Shadow
45
+ 'box-shadow',
46
+ ];
47
+ /**
48
+ * Check if element is visible (has dimensions and not hidden)
49
+ */
4
50
  function isVisible(el) {
5
- while (el) {
6
- // @ts-ignore
7
- if (el === document) {
8
- return true;
9
- }
10
- const style = window.getComputedStyle(el, null);
11
- if (!el || !style) {
12
- return false;
13
- }
14
- if (style.display === 'none' || style.visibility === 'hidden' || +style.opacity === 0) {
15
- return false;
16
- }
17
- if ((style.display === 'block' || style.display === 'inline-block') &&
18
- style.height === '0px' && style.overflow === 'hidden') {
19
- return false;
20
- }
21
- return style.position === 'fixed' || isVisible(el.parentNode);
51
+ const style = window.getComputedStyle(el);
52
+ if (style.display === 'none' ||
53
+ style.visibility === 'hidden' ||
54
+ parseFloat(style.opacity) === 0) {
55
+ return false;
22
56
  }
23
- return false;
57
+ const rect = el.getBoundingClientRect();
58
+ return rect.width > 0 && rect.height > 0;
24
59
  }
25
- function getDomPath(el) {
26
- const stack = [];
27
- while (el.parentNode !== null) {
28
- let sibCount = 0;
29
- let sibIndex = 0;
30
- for (let i = 0; i < el.parentNode.childNodes.length; i++) {
31
- const sib = el.parentNode.childNodes[i];
32
- if (sib.nodeName === el.nodeName) {
33
- if (sib === el) {
34
- sibIndex = sibCount;
35
- }
36
- sibCount++;
37
- }
38
- }
39
- if (el.hasAttribute('id') && el.id !== '') {
40
- stack.unshift(`${el.nodeName.toLowerCase()}#${el.id}`);
60
+ /**
61
+ * Extract relevant attributes from element
62
+ * Skips most data-* attributes to reduce payload size
63
+ */
64
+ function getAttributes(el) {
65
+ const attrs = {};
66
+ for (const attr of Array.from(el.attributes)) {
67
+ // Include data-testid but skip other data-* attributes
68
+ if (!attr.name.startsWith('data-') || attr.name === 'data-testid') {
69
+ attrs[attr.name] = attr.value;
41
70
  }
42
- else if (sibCount > 1) {
43
- stack.unshift(`${el.nodeName.toLowerCase()}:eq(${sibIndex})`);
71
+ }
72
+ return attrs;
73
+ }
74
+ /**
75
+ * Extract computed styles that affect visual appearance
76
+ */
77
+ function getComputedStyles(el) {
78
+ const computed = window.getComputedStyle(el);
79
+ const styles = {};
80
+ for (const prop of STYLES_TO_CAPTURE) {
81
+ const value = computed.getPropertyValue(prop);
82
+ // Skip default/empty values to reduce payload
83
+ if (value && value !== 'initial' && value !== 'none' && value !== 'normal' && value !== 'auto' && value !== '0px' && value !== 'rgba(0, 0, 0, 0)') {
84
+ // Convert kebab-case to camelCase for consistency
85
+ const camelProp = prop.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
86
+ styles[camelProp] = value;
44
87
  }
45
- else {
46
- stack.unshift(el.nodeName.toLowerCase());
88
+ }
89
+ return styles;
90
+ }
91
+ /**
92
+ * Get direct text content (not from children)
93
+ */
94
+ function getDirectText(el) {
95
+ let text = '';
96
+ for (const child of Array.from(el.childNodes)) {
97
+ if (child.nodeType === Node.TEXT_NODE) {
98
+ text += child.textContent?.trim() || '';
47
99
  }
48
- el = el.parentNode;
49
100
  }
50
- return stack.slice(1);
101
+ // Truncate long text
102
+ const trimmed = text.trim();
103
+ if (!trimmed)
104
+ return undefined;
105
+ return trimmed.length > MAX_TEXT_LENGTH ? trimmed.substring(0, MAX_TEXT_LENGTH) + '...' : trimmed;
51
106
  }
52
- function dumpElement(el) {
107
+ /**
108
+ * Recursively collect DOM node and its children
109
+ */
110
+ function collectNode(el, depth = 0) {
111
+ // Limit depth to prevent stack overflow
112
+ if (depth > MAX_DEPTH)
113
+ return null;
114
+ // Skip invisible elements
115
+ if (!isVisible(el))
116
+ return null;
117
+ // Skip non-visual elements
118
+ if (SKIP_TAGS.includes(el.tagName))
119
+ return null;
53
120
  const rect = el.getBoundingClientRect();
121
+ // Collect children
122
+ const children = [];
123
+ for (const child of Array.from(el.children)) {
124
+ const childNode = collectNode(child, depth + 1);
125
+ if (childNode) {
126
+ children.push(childNode);
127
+ }
128
+ }
54
129
  return {
55
- tag: el.tagName,
56
- id: el.id,
57
- top: rect.top,
58
- right: rect.right,
59
- bottom: rect.bottom,
60
- left: rect.left,
61
- width: rect.width,
62
- height: rect.height,
63
- domPath: getDomPath(el),
130
+ tagName: el.tagName.toLowerCase(),
131
+ attributes: getAttributes(el),
132
+ rect: {
133
+ x: Math.round(rect.x),
134
+ y: Math.round(rect.y),
135
+ width: Math.round(rect.width),
136
+ height: Math.round(rect.height),
137
+ },
138
+ computedStyles: getComputedStyles(el),
139
+ children,
140
+ text: getDirectText(el),
64
141
  };
65
142
  }
66
143
  /**
144
+ * Collects DOM tree with computed styles for RCA (Root Cause Analysis)
145
+ * This function runs in browser context via WebdriverIO
146
+ *
147
+ * @param done - Callback function for WebdriverIO async compatibility
148
+ * @returns Serialized DomNode tree as JSON string
67
149
  * @hidden - hidden for typedoc
68
150
  */
69
151
  function getDomDump(done) {
70
- const elements = Array.from(document.body.getElementsByTagName('*')).filter((x) => isVisible(x));
71
- const dumpElements = [];
72
- for (const el of elements) {
73
- dumpElements.push(dumpElement(el));
74
- }
75
- const result = JSON.stringify(dumpElements, null, '\t');
152
+ const root = collectNode(document.body);
153
+ const result = JSON.stringify(root);
76
154
  done(result);
77
155
  return result;
78
156
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syngrisi/wdio-sdk",
3
- "version": "2.6.1",
3
+ "version": "3.0.0",
4
4
  "description": "Syngrisi WebdriverIO SDK",
5
5
  "main": "dist/WDIODriver.js",
6
6
  "types": "dist/WDIODriver.d.ts",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "homepage": "https://github.com/syngrisi/syngrisi/tree/main/packages/wdio-sdk#readme",
40
40
  "dependencies": {
41
- "@syngrisi/core-api": "^2.6.1",
41
+ "@syngrisi/core-api": "^3.0.0",
42
42
  "@wdio/globals": "^9.17.0",
43
43
  "@wdio/types": "^9.20.0",
44
44
  "form-data": "^4.0.5",
@@ -59,5 +59,5 @@
59
59
  "typescript": "^5.9.3",
60
60
  "vitest": "^4.0.15"
61
61
  },
62
- "gitHead": "9c8a083ab2e6026962576cf481847a17e69469bd"
62
+ "gitHead": "a963646a81a5b9a0287a839667b003c2826341ee"
63
63
  }