@dev-blinq/cucumber_client 1.0.1452-dev → 1.0.1452-stage
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/bin/assets/bundled_scripts/recorder.js +73 -73
- package/bin/assets/preload/css_gen.js +10 -10
- package/bin/assets/preload/toolbar.js +27 -29
- package/bin/assets/preload/unique_locators.js +1 -1
- package/bin/assets/preload/yaml.js +288 -275
- package/bin/assets/scripts/aria_snapshot.js +223 -220
- package/bin/assets/scripts/dom_attr.js +329 -329
- package/bin/assets/scripts/dom_parent.js +169 -174
- package/bin/assets/scripts/event_utils.js +94 -94
- package/bin/assets/scripts/pw.js +2050 -1949
- package/bin/assets/scripts/recorder.js +84 -46
- package/bin/assets/scripts/snapshot_capturer.js +147 -147
- package/bin/assets/scripts/unique_locators.js +170 -49
- package/bin/assets/scripts/yaml.js +796 -783
- package/bin/assets/templates/_hooks_template.txt +6 -2
- package/bin/assets/templates/utils_template.txt +16 -16
- package/bin/client/code_cleanup/find_step_definition_references.js +0 -1
- package/bin/client/code_cleanup/utils.js +16 -7
- package/bin/client/code_gen/api_codegen.js +2 -2
- package/bin/client/code_gen/code_inversion.js +119 -2
- package/bin/client/code_gen/duplication_analysis.js +2 -1
- package/bin/client/code_gen/function_signature.js +4 -0
- package/bin/client/code_gen/page_reflection.js +52 -11
- package/bin/client/code_gen/playwright_codeget.js +163 -75
- package/bin/client/cucumber/feature.js +4 -17
- package/bin/client/cucumber/feature_data.js +2 -2
- package/bin/client/cucumber/project_to_document.js +8 -2
- package/bin/client/cucumber/steps_definitions.js +19 -3
- package/bin/client/local_agent.js +1 -0
- package/bin/client/parse_feature_file.js +23 -26
- package/bin/client/playground/projects/env.json +2 -2
- package/bin/client/recorderv3/bvt_init.js +305 -0
- package/bin/client/recorderv3/bvt_recorder.js +1031 -61
- package/bin/client/recorderv3/implemented_steps.js +2 -0
- package/bin/client/recorderv3/index.js +3 -286
- package/bin/client/recorderv3/services.js +818 -142
- package/bin/client/recorderv3/step_runner.js +25 -8
- package/bin/client/recorderv3/step_utils.js +569 -75
- package/bin/client/recorderv3/update_feature.js +87 -39
- package/bin/client/recorderv3/wbr_entry.js +61 -0
- package/bin/client/recording.js +1 -0
- package/bin/client/upload-service.js +4 -2
- package/bin/client/utils/app_dir.js +21 -0
- package/bin/client/utils/socket_logger.js +87 -125
- package/bin/index.js +4 -1
- package/package.json +11 -5
- package/bin/client/recorderv3/app_dir.js +0 -23
- package/bin/client/recorderv3/network.js +0 -299
- package/bin/client/recorderv3/scriptTest.js +0 -5
- package/bin/client/recorderv3/ws_server.js +0 -72
|
@@ -390,6 +390,7 @@ class BVTRecorder {
|
|
|
390
390
|
}
|
|
391
391
|
case "input": {
|
|
392
392
|
const target = this.eventUtils.getNearestInteractiveElement(this.eventUtils.deepEventTarget(event));
|
|
393
|
+
|
|
393
394
|
if (target.nodeName === "INPUT" && target.type.toLowerCase() === "file") {
|
|
394
395
|
return {
|
|
395
396
|
details: {
|
|
@@ -434,6 +435,36 @@ class BVTRecorder {
|
|
|
434
435
|
return;
|
|
435
436
|
}
|
|
436
437
|
case "keydown": {
|
|
438
|
+
// override event.preventDefault to capture the value
|
|
439
|
+
|
|
440
|
+
const oldPreventDefault = event.preventDefault.bind(event);
|
|
441
|
+
event.preventDefault = () => {
|
|
442
|
+
if (event.key.length >= 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
|
443
|
+
const target = this.eventUtils.getNearestInteractiveElement(this.eventUtils.deepEventTarget(event));
|
|
444
|
+
setTimeout(() => {
|
|
445
|
+
if (event.__bvt_recorded !== undefined) return;
|
|
446
|
+
const valueBefore = target.value;
|
|
447
|
+
let newValue = valueBefore;
|
|
448
|
+
|
|
449
|
+
this.recordEvent(
|
|
450
|
+
{
|
|
451
|
+
details: {
|
|
452
|
+
name: "fill",
|
|
453
|
+
text: newValue,
|
|
454
|
+
},
|
|
455
|
+
element: target,
|
|
456
|
+
},
|
|
457
|
+
target,
|
|
458
|
+
"input",
|
|
459
|
+
event
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
event.__bvt_recorded = true;
|
|
463
|
+
}, 20);
|
|
464
|
+
}
|
|
465
|
+
oldPreventDefault();
|
|
466
|
+
};
|
|
467
|
+
|
|
437
468
|
if (!this.eventUtils.shouldGenerateKeyPressFor(event)) return;
|
|
438
469
|
// if (this._actionInProgress(event)) {
|
|
439
470
|
// this._expectProgrammaticKeyUp = true;
|
|
@@ -676,7 +707,8 @@ class BVTRecorder {
|
|
|
676
707
|
|
|
677
708
|
lastInputId = el.dataset.inputId;
|
|
678
709
|
|
|
679
|
-
el.__locators = this.getLocatorsObject(el);
|
|
710
|
+
el.__locators = this.getLocatorsObject(el, { maxLocators: 1 });
|
|
711
|
+
|
|
680
712
|
}
|
|
681
713
|
const role = this.PW.roleUtils.getAriaRole(el);
|
|
682
714
|
const label =
|
|
@@ -684,13 +716,15 @@ class BVTRecorder {
|
|
|
684
716
|
this.PW.roleUtils.getElementAccessibleName(el, true) ||
|
|
685
717
|
"";
|
|
686
718
|
const result = this.getElementProperties(el);
|
|
719
|
+
const elText = this.PW.selectorUtils.elementText(new Map(), el);
|
|
687
720
|
return {
|
|
688
721
|
role,
|
|
689
722
|
label,
|
|
690
723
|
inputID: el.dataset.inputId,
|
|
691
724
|
tagName: el.tagName,
|
|
692
725
|
type: el.type,
|
|
693
|
-
text:
|
|
726
|
+
text: elText.full.trim(),
|
|
727
|
+
textNormalized: elText.normalized.trim(),
|
|
694
728
|
parent: `tagname: ${el.parentElement?.tagName}\ninnerText: ${el.parentElement?.innerText}`,
|
|
695
729
|
attrs: {
|
|
696
730
|
placeholder: el.getAttribute("placeholder"),
|
|
@@ -787,30 +821,31 @@ class BVTRecorder {
|
|
|
787
821
|
return el.matches(closeBtnSelector);
|
|
788
822
|
});
|
|
789
823
|
}
|
|
790
|
-
|
|
791
|
-
|
|
824
|
+
docHasShadowDOM() {
|
|
825
|
+
for (const el of document.all) {
|
|
826
|
+
if (el.tagName.includes("-")) {
|
|
827
|
+
if (["x-bvt-toolbar", "x-bvt-tool"].includes(el.tagName.toLowerCase())) continue;
|
|
828
|
+
if (el.shadowRoot) {
|
|
829
|
+
return true;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
return false;
|
|
834
|
+
}
|
|
835
|
+
getLocatorsObject(el, options = {}) {
|
|
836
|
+
if (!this.docHasShadowDOM()) {
|
|
837
|
+
console.log("No custom elements detected, skipping locator generation");
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
console.log("Custom elements detected locator generation");
|
|
792
841
|
if (this.contextElement) {
|
|
793
|
-
const
|
|
794
|
-
const contextEl = this.contextElement;
|
|
795
|
-
// const { climb, commonParent } = window.getCommonParent(contextEl, el);
|
|
796
|
-
const commonParent = this.locatorGenerator.dom_Parent.findLowestCommonAncestor([contextEl, el]);
|
|
797
|
-
const climb = this.locatorGenerator.dom_Parent.getClimbCountToParent(contextEl, commonParent);
|
|
798
|
-
const result = this.locatorGenerator.getElementLocators(el, {
|
|
799
|
-
excludeText: true,
|
|
800
|
-
root: commonParent,
|
|
801
|
-
});
|
|
802
|
-
result.locators.forEach((locator) => {
|
|
803
|
-
locator.text = text;
|
|
804
|
-
locator.climb = climb;
|
|
805
|
-
});
|
|
806
|
-
result.allStrategyLocators.context = result.locators;
|
|
807
|
-
result.allStrategyLocators.strategy = "context";
|
|
808
|
-
result.allStrategyLocators.no_text = [];
|
|
842
|
+
const result = this.locatorGenerator.toContextLocators(el, this.contextElement, options);
|
|
809
843
|
return result;
|
|
810
844
|
}
|
|
811
845
|
const isRecordingText = this.#mode === "recordingText";
|
|
812
846
|
return this.locatorGenerator.getElementLocators(el, {
|
|
813
847
|
excludeText: isRecordingText,
|
|
848
|
+
...options,
|
|
814
849
|
});
|
|
815
850
|
}
|
|
816
851
|
addListeners() {
|
|
@@ -847,31 +882,7 @@ class BVTRecorder {
|
|
|
847
882
|
}
|
|
848
883
|
|
|
849
884
|
performance.mark("command-send");
|
|
850
|
-
|
|
851
|
-
mode: this.#mode,
|
|
852
|
-
action: action.details,
|
|
853
|
-
element: this.getElementDetails(actionElement, eventName),
|
|
854
|
-
isPopupCloseClick: this.isPopupCloseEvent(e),
|
|
855
|
-
// ...this.getLocatorsObject(actionElement),
|
|
856
|
-
...(actionElement.__locators ?? this.getLocatorsObject(actionElement)),
|
|
857
|
-
frame: this.getFrameDetails(),
|
|
858
|
-
statistics: {
|
|
859
|
-
time: `${performance.measure("command-received", "command-send").duration.toFixed(2)} ms`,
|
|
860
|
-
},
|
|
861
|
-
};
|
|
862
|
-
const snapshotDetails = {
|
|
863
|
-
id: actionElement.getAttribute("data-blinq-id"),
|
|
864
|
-
contextId: this.contextElement?.getAttribute("data-blinq-context-id"),
|
|
865
|
-
doc: this.snapshotCapturer.createSnapshot({
|
|
866
|
-
excludeSelectors: ["x-bvt-toolbar", "script", "style", "link[rel=stylesheet]"],
|
|
867
|
-
}),
|
|
868
|
-
};
|
|
869
|
-
cmd.snapshotDetails = snapshotDetails;
|
|
870
|
-
// eventQueue.enqueue(async () => {
|
|
871
|
-
// await bvtRecorderBindings.validateLocators(snapshotDetails);
|
|
872
|
-
// });
|
|
873
|
-
// console.log(cmd);
|
|
874
|
-
await bvtRecorderBindings.recordCommand(cmd);
|
|
885
|
+
this.recordEvent(action, actionElement, eventName, e);
|
|
875
886
|
this.handleStateTransition(action.element);
|
|
876
887
|
},
|
|
877
888
|
{ capture: true }
|
|
@@ -879,6 +890,33 @@ class BVTRecorder {
|
|
|
879
890
|
});
|
|
880
891
|
}
|
|
881
892
|
|
|
893
|
+
recordEvent(action, actionElement, eventName, e) {
|
|
894
|
+
const cmd = {
|
|
895
|
+
mode: this.#mode,
|
|
896
|
+
action: action.details,
|
|
897
|
+
element: this.getElementDetails(actionElement, eventName),
|
|
898
|
+
isPopupCloseClick: this.isPopupCloseEvent(e),
|
|
899
|
+
...(actionElement.__locators ?? this.getLocatorsObject(actionElement, { maxLocators: 1 })),
|
|
900
|
+
frame: this.getFrameDetails(),
|
|
901
|
+
statistics: {
|
|
902
|
+
time: `${performance.measure("command-received", "command-send").duration.toFixed(2)} ms`,
|
|
903
|
+
},
|
|
904
|
+
};
|
|
905
|
+
const snapshotDetails = {
|
|
906
|
+
id: actionElement.getAttribute("data-blinq-id"),
|
|
907
|
+
contextId: this.contextElement?.getAttribute("data-blinq-context-id"),
|
|
908
|
+
doc: this.snapshotCapturer.createSnapshot({
|
|
909
|
+
excludeSelectors: ["x-bvt-toolbar", "script", "style", "link[rel=stylesheet]"],
|
|
910
|
+
}),
|
|
911
|
+
};
|
|
912
|
+
cmd.snapshotDetails = snapshotDetails;
|
|
913
|
+
// eventQueue.enqueue(async () => {
|
|
914
|
+
// await bvtRecorderBindings.validateLocators(snapshotDetails);
|
|
915
|
+
// });
|
|
916
|
+
// console.log(cmd);
|
|
917
|
+
bvtRecorderBindings.recordCommand(cmd);
|
|
918
|
+
}
|
|
919
|
+
|
|
882
920
|
// TODO: implement the corresponding logic for the below methods
|
|
883
921
|
setPopupHandlers(_popopHandlers) {
|
|
884
922
|
this.popupHandlers = _popopHandlers;
|
|
@@ -897,7 +935,7 @@ class BVTRecorder {
|
|
|
897
935
|
this.interestedElements.clear();
|
|
898
936
|
}
|
|
899
937
|
processAriaSnapshot(snapshot) {
|
|
900
|
-
const matchedElements = this.findMatchingElements(snapshot, this.snapshotElements);
|
|
938
|
+
const matchedElements = this.snapshotUtils.findMatchingElements(snapshot, this.snapshotElements);
|
|
901
939
|
for (const el of matchedElements.values()) {
|
|
902
940
|
const element = el;
|
|
903
941
|
if (element) {
|
|
@@ -1,155 +1,155 @@
|
|
|
1
1
|
class SnapshotCapturer {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
2
|
+
constructor(options = {}) {
|
|
3
|
+
const { inlineImages = true, inlineStyles = true, excludeSelectors = [] } = options;
|
|
4
|
+
// this.options = {
|
|
5
|
+
// inlineImages,
|
|
6
|
+
// inlineStyles,
|
|
7
|
+
// excludeSelectors
|
|
8
|
+
// };
|
|
9
|
+
this.inlineImages = inlineImages;
|
|
10
|
+
this.inlineStyles = inlineStyles;
|
|
11
|
+
this.excludeSelectors = excludeSelectors;
|
|
12
|
+
}
|
|
13
|
+
imageToDataURL(img, document) {
|
|
14
|
+
try {
|
|
15
|
+
// Create canvas to draw the image
|
|
16
|
+
const canvas = document.createElement("canvas");
|
|
17
|
+
canvas.width = img.naturalWidth || img.width;
|
|
18
|
+
canvas.height = img.naturalHeight || img.height;
|
|
19
|
+
const ctx = canvas.getContext("2d");
|
|
20
|
+
|
|
21
|
+
// Draw image to canvas and convert to data URL
|
|
22
|
+
ctx.drawImage(img, 0, 0);
|
|
23
|
+
return canvas.toDataURL("image/png");
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.warn(`Failed to inline image: ${img.src}`, error);
|
|
26
|
+
return img.src; // Fall back to original source
|
|
16
27
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (!this.inlineStyles) return;
|
|
35
|
-
|
|
36
|
-
const stylesheets = Array.from(document.styleSheets);
|
|
37
|
-
for (const sheet of stylesheets) {
|
|
38
|
-
try {
|
|
39
|
-
if (!sheet.href) continue; // Skip inline styles
|
|
40
|
-
const styleEl = document.createElement('style');
|
|
41
|
-
let text = ""
|
|
42
|
-
const cssRules = Array.from(sheet.cssRules || []);
|
|
43
|
-
for (const rule of cssRules) {
|
|
44
|
-
if (rule.cssText) {
|
|
45
|
-
text += rule.cssText + '\n';
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
styleEl.textContent = text;
|
|
49
|
-
|
|
50
|
-
// Replace the link with our new style element
|
|
51
|
-
if (sheet.ownerNode && sheet.ownerNode.parentNode) {
|
|
52
|
-
sheet.ownerNode.parentNode.replaceChild(styleEl, sheet.ownerNode);
|
|
53
|
-
} else if (sheet.ownerNode) {
|
|
54
|
-
// If the owner node is not in the document, just append it
|
|
55
|
-
document.head.appendChild(styleEl);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
} catch (error) {
|
|
59
|
-
console.warn(`Error processing stylesheet: ${sheet.href}`, error);
|
|
60
|
-
}
|
|
28
|
+
}
|
|
29
|
+
processStyles(document) {
|
|
30
|
+
if (!this.inlineStyles) return;
|
|
31
|
+
|
|
32
|
+
const stylesheets = Array.from(window.document.styleSheets);
|
|
33
|
+
for (const sheet of stylesheets) {
|
|
34
|
+
try {
|
|
35
|
+
const el = sheet.ownerNode.cloneNode(true);
|
|
36
|
+
if (!el.href) {
|
|
37
|
+
document.head.appendChild(el);
|
|
38
|
+
} else {
|
|
39
|
+
const rules = Array.from(sheet.cssRules)
|
|
40
|
+
.map((rule) => rule.cssText)
|
|
41
|
+
.join("\n");
|
|
42
|
+
const styleEl = document.createElement("style");
|
|
43
|
+
styleEl.textContent = rules;
|
|
44
|
+
document.head.appendChild(styleEl);
|
|
61
45
|
}
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.warn(`Error processing stylesheet: ${sheet.href}`, error);
|
|
48
|
+
}
|
|
62
49
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const images = document.querySelectorAll('img');
|
|
67
|
-
images.forEach(img => {
|
|
68
|
-
// Skip SVGs and already data URLs
|
|
69
|
-
if (img.src.startsWith('data:') || img.src.endsWith('.svg')) return;
|
|
70
|
-
|
|
71
|
-
// Skip if the image is excluded
|
|
72
|
-
if (this.excludeSelectors.some(selector => img.matches(selector))) return;
|
|
73
|
-
|
|
74
|
-
// Only inline complete images
|
|
75
|
-
if (img.complete && img.naturalWidth !== 0) {
|
|
76
|
-
try {
|
|
77
|
-
img.setAttribute('src', this.imageToDataURL(img, document));
|
|
78
|
-
} catch (e) {
|
|
79
|
-
console.warn(`Failed to process image: ${img.src}`, e);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
removeExcludedElements(document) {
|
|
85
|
-
this.excludeSelectors.forEach(selector => {
|
|
86
|
-
const elements = document.querySelectorAll(selector);
|
|
87
|
-
elements.forEach(el => {
|
|
88
|
-
el.parentNode?.removeChild(el);
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
processComputedStyles(document) {
|
|
93
|
-
const elements = document.querySelectorAll('*');
|
|
94
|
-
|
|
95
|
-
elements.forEach(el => {
|
|
96
|
-
// Skip excluded elements
|
|
97
|
-
if (this.excludeSelectors.some(selector => el.matches(selector))) return;
|
|
98
|
-
|
|
99
|
-
// Get computed style
|
|
100
|
-
const style = window.getComputedStyle(el);
|
|
101
|
-
|
|
102
|
-
// Copy important styles to inline style
|
|
103
|
-
const importantStyles = [
|
|
104
|
-
'display', 'position', 'width', 'height', 'margin', 'padding',
|
|
105
|
-
'color', 'background-color', 'font-family', 'font-size',
|
|
106
|
-
'text-align', 'line-height', 'border', 'box-shadow', 'opacity'
|
|
107
|
-
// Add more styles as needed
|
|
108
|
-
];
|
|
109
|
-
|
|
110
|
-
const inlineStyles = [];
|
|
111
|
-
|
|
112
|
-
importantStyles.forEach(prop => {
|
|
113
|
-
const value = style.getPropertyValue(prop);
|
|
114
|
-
if (value) {
|
|
115
|
-
inlineStyles.push(`${prop}: ${value}`);
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// Set inline style
|
|
120
|
-
if (inlineStyles.length > 0) {
|
|
121
|
-
const currentStyle = el.getAttribute('style') || '';
|
|
122
|
-
el.setAttribute('style', currentStyle + inlineStyles.join('; ') + ';');
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
createSnapshot() {
|
|
128
|
-
// Clone the document to avoid modifying the original
|
|
129
|
-
const docClone = window.document.cloneNode(true);
|
|
50
|
+
}
|
|
51
|
+
processImages(document) {
|
|
52
|
+
if (!this.inlineImages) return;
|
|
130
53
|
|
|
131
|
-
|
|
132
|
-
|
|
54
|
+
const images = document.querySelectorAll("img");
|
|
55
|
+
images.forEach((img) => {
|
|
56
|
+
// Skip SVGs and already data URLs
|
|
57
|
+
if (img.src.startsWith("data:") || img.src.endsWith(".svg")) return;
|
|
133
58
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const doc = docClone;
|
|
59
|
+
// Skip if the image is excluded
|
|
60
|
+
if (this.excludeSelectors.some((selector) => img.matches(selector))) return;
|
|
137
61
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
62
|
+
// Only inline complete images
|
|
63
|
+
if (img.complete && img.naturalWidth !== 0) {
|
|
64
|
+
try {
|
|
65
|
+
img.setAttribute("src", this.imageToDataURL(img, document));
|
|
66
|
+
} catch (e) {
|
|
67
|
+
console.warn(`Failed to process image: ${img.src}`, e);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
removeExcludedElements(document) {
|
|
73
|
+
this.excludeSelectors.forEach((selector) => {
|
|
74
|
+
const elements = document.querySelectorAll(selector);
|
|
75
|
+
elements.forEach((el) => {
|
|
76
|
+
el.parentNode?.removeChild(el);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
processComputedStyles(document) {
|
|
81
|
+
const elements = document.querySelectorAll("*");
|
|
82
|
+
|
|
83
|
+
elements.forEach((el) => {
|
|
84
|
+
// Skip excluded elements
|
|
85
|
+
if (this.excludeSelectors.some((selector) => el.matches(selector))) return;
|
|
86
|
+
|
|
87
|
+
// Get computed style
|
|
88
|
+
const style = window.getComputedStyle(el);
|
|
89
|
+
|
|
90
|
+
// Copy important styles to inline style
|
|
91
|
+
const importantStyles = [
|
|
92
|
+
"display",
|
|
93
|
+
"position",
|
|
94
|
+
"width",
|
|
95
|
+
"height",
|
|
96
|
+
"margin",
|
|
97
|
+
"padding",
|
|
98
|
+
"color",
|
|
99
|
+
"background-color",
|
|
100
|
+
"font-family",
|
|
101
|
+
"font-size",
|
|
102
|
+
"text-align",
|
|
103
|
+
"line-height",
|
|
104
|
+
"border",
|
|
105
|
+
"box-shadow",
|
|
106
|
+
"opacity",
|
|
107
|
+
// Add more styles as needed
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
const inlineStyles = [];
|
|
111
|
+
|
|
112
|
+
importantStyles.forEach((prop) => {
|
|
113
|
+
const value = style.getPropertyValue(prop);
|
|
114
|
+
if (value) {
|
|
115
|
+
inlineStyles.push(`${prop}: ${value}`);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Set inline style
|
|
120
|
+
if (inlineStyles.length > 0) {
|
|
121
|
+
const currentStyle = el.getAttribute("style") || "";
|
|
122
|
+
el.setAttribute("style", currentStyle + inlineStyles.join("; ") + ";");
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
createSnapshot() {
|
|
127
|
+
// Clone the document to avoid modifying the original
|
|
128
|
+
const docClone = window.document.cloneNode(true);
|
|
129
|
+
|
|
130
|
+
// Store the original document
|
|
131
|
+
const originalDoc = window.document;
|
|
132
|
+
|
|
133
|
+
// Temporarily "swap" the document for processing
|
|
134
|
+
// (We're just using this as a convention - it doesn't actually replace the global document)
|
|
135
|
+
const doc = docClone;
|
|
136
|
+
|
|
137
|
+
// Process the clone
|
|
138
|
+
this.processStyles(doc);
|
|
139
|
+
this.processImages(doc);
|
|
140
|
+
this.removeExcludedElements(doc);
|
|
141
|
+
this.processComputedStyles(doc);
|
|
142
|
+
|
|
143
|
+
// Generate HTML with doctype
|
|
144
|
+
const doctype = originalDoc.doctype
|
|
145
|
+
? new XMLSerializer().serializeToString(originalDoc.doctype)
|
|
146
|
+
: "<!DOCTYPE html>";
|
|
147
|
+
|
|
148
|
+
// Get the HTML content
|
|
149
|
+
const htmlContent = doc.documentElement.outerHTML;
|
|
150
|
+
|
|
151
|
+
// Combine doctype and HTML content
|
|
152
|
+
return `${doctype}${htmlContent}`;
|
|
153
|
+
}
|
|
154
154
|
}
|
|
155
|
-
export default SnapshotCapturer;
|
|
155
|
+
export default SnapshotCapturer;
|